基于tcp协议的网络通信(将服务端守护进程化)

目录

守护进程化

引入

介绍

如何实现 

思路

接口 -- setsid

注意点 

实现代码

daemon.hpp

log.hpp

运行情况


前情提要 -- 前后台任务介绍(区别+命令),session+sid介绍,session退出后的情况(nuhup,终端进程控制组),任务+进程组概念,任务与进程组的关系,-bash介绍-CSDN博客

守护进程化

引入

如果让他们可以不受这些的影响,这就是守护进程化

介绍

是一种使进程脱离终端会话控制,并在后台持续运行的技术

  • 守护进程通常是作为服务在系统中运行的后台进程,它们不会受到终端会话退出的影响,并且可以在系统启动时自动启动

如何实现 

思路

我们已经知道,启动的任务会自动属于当前的会话 -> 那么就会随着会话的退出而受到影响,且这个影响未知(有可能被终止,有可能还存在)

  • 如果我们想要让启动的任务不受到会话的影响,就可以让他自成一个会话
  • 也就是我们自己创建一个新会话,把它迁移过去
  • 这样,这个任务就不会受到原来那个会话的影响了,因为它已经归属其他会话了
  • (当然,这个新的会话不需要与键盘文件交互)

接口 -- setsid

创建一个会话,让当前进程脱离当前会话,成为新会话的进程组,并且让调用这个函数的进程的pgid作为sid

但他有一个条件,进程组的组长不能调用这个函数

  • 就相当于 -- 你在公司如果是个组长的职位,你告诉老板说自己想出去单干,老板肯定不愿意,他会说,你走不行,你下面的组员走可以
  • 所以,结合任务中第一个进程作为组长来看,我们可以考虑借助父子进程的特性来实现
  • 父进程会是组长,让他退出
  • 子进程作为组员,去执行守护进程化的工作
  • (毕竟父进程干等着也没啥用,工作是子进程的,唯一需要的就是释放子进程的资源,但这一工作可以让init进程干,也就是托孤给它)

注意点 

如果我们将服务端变成守护进程,那我们是不需要它在显示器上打印

  • 所以我们可以考虑将标准输出/错误关闭
  • 标准输入也不需要,因为后台任务本身就无法和键盘交互

但我们的服务端之前写过两种需要的打印:日志和debug语句

  • 如果直接关闭文件描述符,会导致写入错误
  • 所以,可以将他们重定向

如果需要日志的话,可以将它重定向到log.txt文件里(注意,下面的代码我设置的log.txt是放在log目录里的,需要手动创建个目录,不然日志文件创建不了)

但dug语句确实不需要了,其中有两种做法:

  • 删除相关代码(但在代码量过大时,就不太方便了)
  • 将标准流重定向到/dev/null (它会将收到的数据直接丢弃,相当于垃圾桶)

当然,不要忘记处理SIGCHLD和SIGHUP这两个信号的处理方式

  • 因为我们的代码中可能会涉及到这两个信号:父子进程,终端退出等
  • 其他信号根据自己来设定,如果不希望这个服务端被暂停,也可以忽略SIGSTOP信号]

除此之外,守护进程也许需要修改自己的工作目录

实现代码

daemon.hpp

#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 null_path = "/dev/null";

void daemon(const std::string &path = "")
{
    // 忽略信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    // 创建子进程
    if (fork() > 0)
    {
        exit(0);
    }
    // 子进程成为守护进程
    setsid();

    // 修改工作目录
    if (!path.empty())
    {
        chdir(path.c_str());
    }

    // 重定向
    int fd = open(null_path.c_str(), O_RDWR);
    if (fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

log.hpp

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define INFO 0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4 // 致命的错误

#define SCREEN 1
#define ONEFILE 2

#define DEF_NAME "log.txt"
#define DEF_PATH "./log/"

#define SIZE 1024

class Log
{
public:
    Log()
        : method_(SCREEN), path_(DEF_PATH)
    {
    }
    void enable()
    {
        method_ = ONEFILE;
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        printLog(logtxt);
    }
    ~Log()
    {
    }

private:
    std::string levelToString(int level)
    {
        switch (level)
        {
        case INFO:
            return "INFO";
        case DEBUG:
            return "DEBUG";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "NONE";
        }
    }
    void printLog(const std::string &logtxt)
    {
        switch (method_)
        {
        case SCREEN:
            std::cout << logtxt << std::endl;
            break;
        case ONEFILE:
            printOneFile(logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &info)
    {
        std::string path = path_ + DEF_NAME;
        int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        //int fd = open("DEF_NAME", O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd > 0)
        {
            write(fd, info.c_str(), info.size());
            close(fd);
        }
        else
        {
            return;
        }
    }

private:
    int method_;
    std::string path_;
};

Log lg;

运行情况

服务端成功变成守护进程(孤儿进程+与终端无关+自成会话)

并且,随着客户端的登录/退出,log.txt也成功增加内容:

守护进程化的接口 -- daemon

参数

他有两个参数:

  • 如果想要把工作目录更换至根目录,nochdir=0;否则为当前路径
  • 如果想要将三个标准流重定向至/dev/null,noclose=0;否则不会对他们做处理

返回值 

  • 成功返回0
  • 失败返回-1,并设置错误码
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值