【Linux】实现守护进程 | 以tcpServer为例

news2024/11/24 3:52:21

本文首发于 慕雪的寒舍

本文将以tcp服务器代码为基本,讲述如何将进程守护进程化,后台运行

1.守护进程

所谓守护进程,就是和其他进程没有关系的进程;其独立运行于系统后台,除非自己退出或收到信号终止,否则会一直运行下去

1.1 进程组

在我们使用的bash中,同一时刻只会有一个前台进程组

image-20230209101802180

如图,当一个前台进程开始运行之后,我们没有办法在当前终端开启第二个前台进程

在运行的命令后面加&,临时让当前进程在后台运行。注意,此时tcp虽然在后台运行了, 但对于它而言,stdin/stdout/stderr的文件描述符依旧指向的是当前bash的输入输出,所以它的日志依旧会打印到当前终端上。

ps命令查看当前进程的信息,其中ppid是当前进程的父进程,也就是当前bash,pid是进程编号,pgid是进程的组编号,可以看到这个组编号和grep命令的组编号是不同的。

image-20230209103412568

我们用这个c语言的代码调用两次fork,相当于创建了3个子进程。

#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    fork();
    fork();
    sleep(100);
    return 0;
}

此时再来查看进程信息,能看到这4个进程的进程组pgid是相同的,而且和第一个test的pid相同;这说明第一个test就是父进程,后面的3个都是子进程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiI0XFdN-1681715550272)(https://img.musnow.top/i/2023/02/63e45da7770c3.png)]

1.2 进程会话

这里还有一个我们之前没有太多了解的信息,进程的sid是什么?

还是上面的例子,在图中能看到,我们执行的test和grep的sid都是相同的,而且都等于第一个test进程的ppid(bash的pid)

image-20230209104248061

这表明图中的5个进程同属于一个进程会话,这个会话就是我们当前打开的bash,并用sid来表示进程会话;

这也是为什么我们登录linux的时候一定会有一个终端,linux系统就是创建会话并加载bash,来给用户提供服务的。

既然存在会话,那就肯定会有会话的资源上限。一旦满了,就会开始杀掉一些进程

./test &

即便我们用&让进程在后台运行,其也有可能收到会话的创建/关闭的影响而被操作系统干掉🧐比如我们将当前正在运行进程的bash关掉,其前台进程会被直接终止,后台进程也会受到影响(有可能终止有可能不终止,取决于系统)

这和我们对tcp服务器的需求不一致:我们需要的是让tcp服务器的进程能一直稳定的在后台运行,让操作系统别去管它;除非系统内存满了,负载重到实在没有办法的时候,操作系统才能过来把他刀了。

为了不让守护进程受到进程会话的影响,我们就必须让其能够独立出来,自成一个进程组和一个新会话

👆这种独立的进程,就可以被称为守护进程/精灵进程

2.实现

2.1 自己写

别以为写这个很难哦,实际特别简单!

2.1.1 setsid

这里需要用到的setsid接口,其作用如名字一般,是设置当前进程的进程会话组

       #include <unistd.h>
       pid_t setsid(void);

但是调用这个函数有一个要求:调用的进程不能是进程组的组长!

比如下图中,第一个test就是进程组的组长,它不能调用这个函数。会报错

image-20230209104248061

那要怎么让自己不成为进程组的组长呢?很简单,创建一个子进程就ok了!

    if (fork() > 0)
        exit(0);//父进程直接退出

2.1.2 重定向到dev/null

如果你不知道什么是/dev/null,简而言之,这是一个linux下的数据垃圾桶。和windows的回收站会存放删除的资料不同,这个垃圾桶是个黑洞,丢进去的东西不会被存放,是直接丢弃的!

守护进程需要把默认的0.1.2文件描述符都重定向到dev/null,是因为设置成独立的进程组和进程会话了之后,当前进程是没有和bash关联的。

此时,默认这个0 1 2所指向的bash是无效的!如果不重定向,使用cout打印的时候,就会引发异常(可以理解为往一个不存在的文件中写内容),服务器直接退出了,无法实现守护进程。

重定向了之后,所有的打印输出都会被丢到/dev/null这个文件垃圾桶中,也就不需要担心上述的问题。

    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. 关闭掉不需要的fd
        // 因为fd只是临时用于重定向,操作完毕了就可以关掉了
        if(fd > STDERR_FILENO) 
            close(fd);
    }

你可能会疑惑,那日志信息也被丢到垃圾桶里面了,怎么办?

很简单,因为我们服务器的日志都统一使用了log.hpp里面的logging函数,所以只需要对logging函数的输出重定向到日志文件里面,就ok了!

2.1.3 chdir(选做)

这个操作的目的是修改工作路径。作为服务器进程,很多日志信息是存放在/etc/目录而不是当前路径下的,为了安全,也应该使用绝对路径而不用相对路径,避免出现工作目录切换而导致的无法读写文件的问题

不过,如果使用绝对路径,即便我们不修改工作目录,也是能正常访问的;所以这个操作是选做的

2.1.4 信号捕捉

自己写这个函数有个好处,那就是我们可以在里面自定义捕捉一些信号,给这些信号 加上自己的自定义方法;

比如SIGPIPE就是管道的信号,当管道的读端关闭的时候,写端会被终止;此时写端就会收到这个信号。如果不对这个信号进行SIG_IGN忽略,我们的服务器会直接终止!

signal(SIGPIPE, SIG_IGN);

除了这个信号,我们还可以对2号或者3号信号进行自定义捕捉,设定退出信号,让服务器能够安全退出(保存日志信息到磁盘,释放资源等;虽然进程退出之后操作系统会帮我们干这些事,但我们这么写能让项目更规范)

2.1.5 完整代码

#pragma once

#include <iostream>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <fcntl.h> // O_RDWR 需要

void daemonize()
{
    int fd = 0;
    // 1. 忽略SIGPIPE (管道读写,读端关闭,写端会收到信号终止)
    signal(SIGPIPE, SIG_IGN);
    // 2. 更改进程的工作目录
    // chdir(); // 可以改,可以不改
    // 3. 让自己不要成为进程组组长
    if (fork() > 0)
        exit(0);
    // 4. 设置自己是一个独立的会话
    setsid();
    // 5. 重定向0,1,2
    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. 关闭掉不需要的fd
        // 因为fd只是临时用于重定向,操作完毕了就可以关掉了
        if(fd > STDERR_FILENO) 
            close(fd);
    }
    // 这里还有另外一种操作,就是把stdin/stdout/stderr给close了
    // 但是这样会导致只要有打印输出的代码,进程会就异常退出
}

没错,就这一点点代码,就能让我们的tcp服务器变成守护进程!

image-20230209134324709

此时我们的客户端依旧能正常连接服务端,获取结果

image-20230209134353714

2.2 nohup

no hang up(不挂起),用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。用nohup命令执行一个进程,就能让这个进程成为不受终端退出影响的进程

nohup ./test &

此时,nohup会在当前目录下创建一个nohup.out文件,用于记录test进程的输出信息(如果通过了>>>执行了重定向,则不会创建)

image-20230209110836838

通过ps可已看到,当前test进程的进程会话还是和bash相同,但我们关闭当前bash,这个test进程依旧能正常运行,只不过父进程会变成操作系统1,我们的目的也算是达到了

image-20230209111411664

2.3 deamon接口

linux系统中有一个接口daemon,可以帮我们实现守护进程

       #include <unistd.h>
       int daemon(int nochdir, int noclose);

了解过守护进程的写法了之后,这两个参数的作用就很明显了

  • 第一个参数nochdir表明是否需要修改工作目录;如果设置为0,则切换工作目录到/系统根目录
  • 第二个参数noclose表明是否需要重定向基础io到/dev/null;设置为0则重定向

以下是man手册中的说明

If nochdir is zero, daemon() changes the calling process's current working directory to the root directory ("/"); otherwise, the  cur‐
rent working directory is left unchanged.

If noclose is zero, daemon() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are made
to these file descriptors.

我们直接用一个简单代码来演示

#include <unistd.h>

int main()
{
    //不需要修改工作目录,第一个参数设为1
    //因为没有进行打印,重定向设置成1,不进行重定向
    int ret = daemon(1,1);
    sleep(100);
    return 0;
}

运行之后可以看到,这个进程的父id是操作系统,其自成一个进程组和进程会话;和我们自己写的函数作用相同

image-20230209131905946

3.重定向log

因为守护进程把输入输出丢到了垃圾捅里面,所以我们就需要重定向日志的输出

#define LOG_PATH "./log.txt" //工作路径下的log.txt

// 这个类只用于重定向,不需要在里面加其他东西
class Logdup
{
public:
    Logdup()
        :_fdout(-1),_fderr(-1)
    {}
    Logdup(const char* pout=LOG_PATH,const char* perr="")
        :_fdout(-1),_fderr(-1)
    {
        //如果只传入了第一个pout,则代表将perr和pout重定向为一个路径
        umask(0);
        int logfd = open(pout, O_WRONLY | O_CREAT | O_APPEND, 0666);
        assert(logfd != -1);
        _fdout = _fderr = logfd;//赋值可以连等
        //判断是不是空串
        if(strcmp(perr,"")!=0)//不相同,代表单独设置了err的路径
        {
            logfd = open(perr, O_WRONLY | O_CREAT | O_APPEND, 0666);
            assert(logfd != -1);
            _fderr = logfd;
        }
        dup2(_fdout, 1);//重定向stdout
        dup2(_fderr, 2);//重定向stderr
    }

    ~Logdup()
    {
        if(_fdout!= -1)
        {
            fsync(_fdout);
            fsync(_fderr);
            // 先写盘再关闭
            close(_fdout);
            close(_fderr);
        }
    }
private:
    int _fdout;//重定向的日志文件描述符
    int _fderr;//重定向的错误文件描述符
};c

做完这一切之后,我们运行服务器,的确创建了log.txt文件,可里面空空如也

image-20230209143230108

这是因为我们的数据其实都被写道了缓冲区里面,我们需要在logging里面添加一个刷新机制,才能让数据尽快写入到硬盘中,避免日志丢失

    fflush(out); // 将C缓冲区中的数据刷新到OS
    fsync(fileno(out));// 将OS中的数据写入硬盘

此时再运行服务器,就能看到日志很快被写入文件里面了。

image-20230209144101425

over

搞定啦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/424749.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

逆向-还原代码之(*point)[4]和char *point[4] (Arm 64)

// source code #include <stdio.h> #include <string.h> #include <stdlib.h> /* * char (*point)[4] // 数组指针。 a[3][4] // 先申明二维数组,用它来指向这个二维数组 * char *point[4] // 指针数组。 a[4][5] // 一连串的指针…

编程语言,TIOBE 4 月榜单:黑马出现了

TIOBE 4 月榜单已经发布了&#xff0c;一起来看看这个月编程语言排行榜有什么变化吧&#xff01; C 发展依旧迅猛 在本月榜单中&#xff0c;TOP 20 的变动不大&#xff0c;Python、C、Java 、 C 和C#依然占据前五。甚至排名顺序都和上个月一样没有变动。 同时&#xff0c;Rus…

【图形学】多边形裁剪算法综述

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本文是个人学习多边形裁剪知识整理的&#xff0c;整理期间努力理解论文作者含义&#xff0c;并增加了自己的详述和注解。 &#x1f970;来源&#xff1a;材料主要源于多边形裁剪相关论文进行的&#xff0c;每个知识点的学习…

第三十一天 Linux介绍和基础命令

目录 1.前言 1.1 什么是Linux 1.2 为什么要学Linux 1.3 学完Linux能干什么 2.Linux简介 2.1 主流操作系统 2.2 Linux发展历史 3. Linux安装 3.1 安装方式介绍 3.2 安装VMware 3.3 安装Linux 3.4 网卡设置 3.5 安装SSH连接工具 3.6 Linux目录结构 4.Linux常用命令…

洛谷P8772 [蓝桥杯 2022 省 A] 求和 C语言/C++

[蓝桥杯 2022 省 A] 求和 题目描述 给定 nnn 个整数 a1,a2,⋯,ana_{1}, a_{2}, \cdots, a_{n}a1​,a2​,⋯,an​, 求它们两两相乘再相加的和&#xff0c;即 Sa1⋅a2a1⋅a3⋯a1⋅ana2⋅a3⋯an−2⋅an−1an−2⋅anan−1⋅anSa_{1} \cdot a_{2}a_{1} \cdot a_{3}\cdotsa_{1} \cd…

SpringCloud学习(五)——Nacos配置管理

文章目录1. Nacos实现配置管理2. 微服务拉取配置2.1 拉取优先级2.2 导入依赖2.3 添加注解2.4 配置热更新3. 使用 ConfigurationProperties3.1 使用注解3.2 测试4. 多环境共享配置4.1 添加依赖4.2 配置文件4.3 更改属性4.4 测试4.5 配置优先级1. Nacos实现配置管理 当微服务部署…

【测试面试】吐血整理,大厂测试开发岗面试题(1~4面),拿下年40w...

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化测试面试题&am…

scConverter 文档转换 DLL / SDK 2023.3.21 Crack

scConverter 转换 DLL / SDK scConverter 是一个DLL&#xff0c;可以将PDF、DWF、Gerber、CGM、TIFF、CALS、PLT、PNG和JPEG文件转换为大量输出格式。可用的输出格式列表包括Adob​​e PDF、PDF/A、DXF、DWF、CALS、TIFF、PLT和PNG。您将在下面找到所有可用输入和输出格式的完整…

计算机算法设计与分析(第5版)PDF

《计算机算法设计与分析&#xff08;第5版&#xff09;》是2018年电子工业出版社出版的图书&#xff0c;作者是王晓东。 整本书的结构是&#xff1a;先介绍算法设计策略思想&#xff0c;然后从解决经典算法问题来学习&#xff0c;通过实践的方式去学习算法。 网络上许多的算法…

天梯赛-模拟赛-4.16

L2-041 插松枝 人造松枝加工场的工人需要将各种尺寸的塑料松针插到松枝干上&#xff0c;做成大大小小的松枝。他们的工作流程&#xff08;并不&#xff09;是这样的&#xff1a; 每人手边有一只小盒子&#xff0c;初始状态为空。 每人面前有用不完的松枝干和一个推送器&#x…

【鸿蒙应用ArkTS开发系列】- Web组件使用讲解

目录 一、Web组件介绍 二、创建组件 权限列表 三、设置样式和属性 四、添加事件和方法 五、访问本地Html 1、本地html文件创建 2、本地html文件加载 2、JS对象注入&#xff0c;Html使用JS对象调用客户端方法 3、客户端调用本地Html网页中的JS方法 使用鸿蒙的ArkUI框架…

Lesson 10.1 超参数优化与枚举网格的理论极限和随机网格搜索 RandomSearchCV

文章目录一、超参数优化与枚举网格的理论极限1. 超参数优化 HPO&#xff08;HyperParameter Optimization&#xff09;2. 网格搜索的理论极限与缺点3. 建立 benchmark&#xff1a;随机森林中枚举网格搜索的结果二、随机网格搜索 RandomizedSearchCV1. 基本原理2. 随机网格搜索的…

使用chatgpt实现微信聊天小程序(秒回复),github开源(附带链接)

文章目录前言效果展示原理说明服务器端代码说明微信小程序代码说明代码链接总结前言 我在前一段时间突发奇想&#xff0c;就使用java来调用chatgpt的接口&#xff0c;然后写了一个简单小程序&#xff0c;也上了热榜第一&#xff0c;java调用chatgpt接口&#xff0c;实现专属于…

select 排序qsort排序

目录 1.希尔排序的时间复杂度 3.有技巧的选择排序&#xff1a;堆排序 4.排序的种类 5.直接插入排序和冒泡排序 6.快速排序 7.希尔排序 堆排序 和快排的区别 8.为什么相遇位置一定比key小 9.快排的优化 11.快排递归写法的不足 12.快排的非递归解法 1.希尔排序的时间复杂…

C++11新特性(上)

357089 文章目录1. 统一的列表初始化1.1 &#xff5b;&#xff5d;初始化1.2 std::initializer_list2. decltype3. 右值引用和移动语义3.1 左值引用和右值引用3.2 左值引用与右值引用比较3.3 右值引用使用场景和意义3.4 右值引用引用左值及更深入的使用场景3.5 完美转发4. 新的…

“编程式 WebSocket” 实现简易 online QQ在线聊天项目

目录 一、需求分析与演示 1.1、需求分析 1.2、效果演示 二、客户端、服务器开发 2.1、客户端开发 2.2、服务器开发 一、需求分析与演示 1.1、需求分析 需求&#xff1a;实现一个 online QQ在线聊天项目&#xff0c;当用户登录上自己的账号后&#xff0c;将会显示在线&…

我用nodejs和electron实现了一个简单的聊天软件-----chat 开源

翎&#x1f3a5;项目演示地址 &#x1f517;https://www.bilibili.com/video/BV1Fg4y1u76d/ 希望观众老爷给个免费的三连支持一下新人up主 ♻️项目基本介绍 翎是基于electron(vue2)和nodejs实现的简单聊天软件,其中用websocket和http进行通讯传递,数据库使用了mysql数据库,…

二进制插入与查找组成一个偶数最接近的两个素数

二进制插入 链接&#xff1a;二进制插入_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a;给定两个32位整数n和m&#xff0c;同时给定i和j&#xff0c;将m的二进制数位插入到n的二进制的第j到第i位,保证n的第j到第i位均为零&#xff0c;且m的二进制位数小于等于i-j1&#xff…

Qt Quick - Popup

Qt Quick - Popup使用总结一、概述二、Popup 的布局三、弹出分级四、弹出定位五、定制化一、概述 Popup是类似弹出式用户界面控件的基本类型。它可以与Window或ApplicationWindow一起使用。 import QtQuick.Window 2.2import QtQuick.Controls 2.12ApplicationWindow {id: win…

力推美团企业版 美团究竟意欲何为?

已经拥有930万活跃商家的美团公司&#xff0c;正在充分整合自身的“供应链”优势&#xff0c;冲向B端市场。 3月31日&#xff0c;据36氪消息显示&#xff0c;美团将于近期正式上线面向To B市场的业务“美团企业版”&#xff0c;定位企业消费赛道。美团企业版会为企业客户提供消…