自学内容网 自学内容网

服务器守护进程化

目录

一、守护进程的定义与特点

1、定义

2、特点

二、守护进程的原理

三、守护进程与会话(Session)的关系

四、C++实现守护进程

     守护进程(Daemon Process)是一个在后台运行、通常不与用户直接交互的进程。守护进程是操作系统中非常重要的一部分,常见的应用包括系统日志、网络服务、数据库管理等。在这篇博客中,我们将详细探讨守护进程的原理、如何与会话管理联系,并通过C++实现一个简单的守护进程。将服务器守护进程化的主要目的是确保服务器在后台持续运行,并在意外崩溃或重启后自动恢复。这样可以使服务在没有人工干预的情况下长期稳定运行,并减少系统管理的复杂度。

一、守护进程的定义与特点

1、定义

守护进程是一个没有终端控制的进程,它通常在系统启动时启动,独立于任何用户会话(session),并且在后台持续运行。守护进程的特点是它不依赖于用户的输入输出,运行时不会产生终端交互。

2、特点

  • 后台运行:守护进程通常在操作系统启动时启动,或者在用户登录后由系统服务启动,并在后台持续运行。
  • 无终端:守护进程不与任何终端或用户会话关联,它通常不与标准输入输出(stdin, stdout, stderr)相关联。
  • 独立性:守护进程与用户的登录会话是独立的,它在后台静静运行,执行系统级任务,如日志记录、定时任务、文件清理等。
  • 父进程为init进程:守护进程在系统启动时由父进程(通常是init进程)启动,运行时不会退出。
  • PID(进程ID):守护进程的PID是由操作系统分配的,它通常会被写入到某个文件中以供后续管理和终止。

二、守护进程的原理

守护进程是通过脱离控制终端、使自己成为一个独立的后台进程来实现的。这是通过几个步骤实现的:

  1. 创建子进程:守护进程首先会创建一个子进程,父进程退出,子进程继续执行,这样可以让守护进程避免与任何用户会话或终端直接交互。

  2. 创建新的会话(Session):守护进程通常会调用 setsid() 系统调用创建一个新的会话,成为该会话的首进程。新会话的创建意味着它不再与原始的控制终端和进程组相关联。

  3. 改变工作目录:守护进程通常会调用 chdir() 来更改工作目录。为了避免占用终端的目录,守护进程通常会将工作目录更改为 /

  4. 关闭文件描述符:守护进程还会关闭与终端相关的文件描述符,包括标准输入(stdin)、标准输出(stdout)、标准错误(stderr)。通常会将它们重定向到 /dev/null,以防止输出到终端。

  5. 忽略信号:守护进程会设置适当的信号处理,避免因收到如SIGHUP等信号导致进程退出。

void Daemon(const std::string &cwd = "")
{
    // 1. 忽略其他异常信号 
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 将自己变成独立的会话
    if (fork() > 0)//>0说明是父进程,让父进程直接退出
        exit(0);
    setsid(); //子进程

    // 3. 更改当前调用进程的工作目录
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 标准输入,标准输出,标准错误重定向至/dev/null 垃圾桶
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

三、守护进程与会话(Session)的关系

会话(Session)是与进程、终端和进程组密切相关的概念。会话的作用是管理一组相关的进程。

  1. 会话的创建:每个登录的用户会话都有一个会话ID(Session ID),一个会话可以有多个进程组(Process Group),而每个进程组中的进程共享同一个控制终端。

  2. 脱离控制终端:守护进程通过 setsid() 系统调用来脱离当前会话及其控制终端,成为一个新的会话的首进程。这样,守护进程就不再与任何终端关联,它可以自由地运行而不受用户的控制。

  3. 控制终端:一旦守护进程创建了新的会话并成为首进程,它就不再与任何控制终端关联。控制终端通常与用户的登录会话相关联,但守护进程会断开这一关系,避免终端输入或输出干扰其运行。

  4. 进程组与信号处理:会话中的进程通常共享进程组,而进程组的控制由会话首进程管理。守护进程通常会设置信号处理机制,使其能够管理来自进程组的信号。


四、C++实现守护进程

#pragma once

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string nullfile = "/dev/null";

void Daemon(const std::string &cwd = "")
{
    // 1. 忽略其他异常信号 
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 将自己变成独立的会话
    if (fork() > 0)//>0说明是父进程,让父进程直接退出
        exit(0);
    setsid(); //子进程

    // 3. 更改当前调用进程的工作目录
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 标准输入,标准输出,标准错误重定向至/dev/null 垃圾桶
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}
#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大
extern Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
    {
    }
    void InitServer()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensock_: %d", listensock_);

        int opt = 1;
        setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));
        // local.sin_addr.s_addr = INADDR_ANY;

        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg(Info, "bind socket success, listensock_: %d", listensock_);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg(Info, "listen socket success, listensock_: %d", listensock_);
    }
    void Start()
    {
        Daemon();
        ThreadPool<Task>::GetInstance()->Start();
        // for fork();
        // signal(SIGCHLD, SIG_IGN);
        lg(Info, "tcpServer is running....");
        for (;;)
        {
            // 1. 获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            // version 4 --- 线程池版本
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }
    ~TcpServer() {}

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

表示服务已经启动


原文地址:https://blog.csdn.net/m0_53830389/article/details/144333465

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!