**前言:**本节主要内容是学习如何让一个服务编程守护进程一直运行在后台。 我们之前实现过tcp服务, 但是如果会话退出后这个服务就停止了。 很明显不符合需求。 所以我们就要学习本节内容学习------如何做到让一个服务编程守护进程!下面废话不多说, 开始我们的学习吧!
ps:本节内容很简单, 友友们可以放心观看哦!
目录
前后台进程的概念
我们知道,当我们利用xshell登录远程主机之后, 我们就会默认有一个bash命令行输入命令。这其实是linux系统自动为我们生成了一个会话。bash就是这个会话里面的一个进程。 而且这个进程默认是打开的,在前台运行的。
我们也知道,进程运行的时候, 再执行命令, 就没有什么用了。但是可以使用ctrl + C终止进程。
为什么会有上面两种情况, 是因为我们在生成会话的时候, bash会被默认启动在前台, bash就可以为我们提供命令行服务。 这个时候bash和键盘相关,我们用户只需要拿着键盘向里面输入数据就行了。然后bash解释后, 就会去执行相应的命令。
然后当我们启动一个进程, 这个时候就会由这个新的进程作为前台进程,bash进程就被挤掉了, 变成后台进程。 这个时候和键盘相关的是新进程, 我们再输入指令, 就是向新进程中输入指令, 新进程没有反应。 但是只对ctrl + c有反应。
然后上面两段话解释了两个结论, 也是事实:
- 一个会话(session)里面,只有一个前台进程,多个后台任务。
- 一个会话(session), 键盘信号只会发给前台进程。
综上,为什么运行一个程序的时候ctrl + C可以终止,但是其他命令没有用呢,就是因为我们bash运行一个程序,然后bash就自动变成后台,然后运行的程序就变成前台了。这个时候再输入指令,就是输入给了前台进程, 也就是我们的程序!
所以, 什么叫做前台:谁拥有标准输入(键盘)谁就是前台。
前后台操作
可以在运行某个程序的指令后面加&, 表示后台运行某个程序。运行这个程序之后, 还会显示一个当前进程的后台任务号:
接下来我们多创建几个后台任务:
这个时候我们想要知道我们的会话中有哪些后台任务, 就可以使用jobs命令查看:
上面是jobs查看后台进程, 我们可不可以让后台进程提到前台来呢? 答案是可以的,就是使用fg这个指令, fg + 后台任务号。就可以让后台进程提到前面来:
然后我们再看。
如果我们把某一个任务暂停了的时候, 这个时候bash进程就会默认被推到前台来。
为什么会这样? 为什么默认是bash呢? 如果前台进程是一个我们自己写的进程, 我们暂停了这个进程,然后操作系统也不管, bash就继续呆在后台。那么这个时候整个会话里面就都是后台了,意味着没有进程能够在键盘里面获取输入。 这是不合理的, 所以我们就要让bash默认提到前台。
同时, 如果让暂停的任务重新启动,那么他就会默认启动在后台。
什么是守护进程
我们要理解什么是守护进程, 就要先看一些铺垫。 我们先看这张图片:
这张图片,process的pid是第二列。 然后PGID是进程组id, SID是session id。 TPGID以及后面的我们不管。
在这张图片中, 我们可以看到我们自己单独启动的process是自成一组, 另外一起创建的sleep是把多个进程之间的第一个进程的pid作为组id。 所以, 进程和进程之间也可以组成一个进程组。(进程组和任务的关系是什么? 任务在现实世界, 可能由一个人来完成, 也可以由一个团队来完成。 对应的就是一个任务由一个进程组来完成,这个进程组可能有一个人, 可能有多个人。
每一个用户登录时都有一个session, 登陆时创建一个session, 退出时销毁一个session。 所以这么多session, 在操作系统层面上, 就要管理创建的session。 所以就要创建session结构体, 然后里面的sessionid就是表示哪一个session。
现在我们实验一个问题,就是我们一个会话如果启动了多个后台进程,当这个会话退出时,这些进程会不会退出。 我们现在创建两个会话, 一个会话用来创建进程,一个会话用来观察系统中还有没有后台进程。 一开始创建进程后:
然后我们删除会话:
我们就能看到, 会话会出, 连带着会话里面创建的进程也被退出了。说明这些进程受到了用户登录和推出的应i想, 如果不想受到用户登陆和注销的影响, 就要使用守护进程。
那么什么是守护进程?
其实就是上面的一张图, 一开始有一个会话, 然后这个绘画里面有一个bash进程, 有其他的自己运行的进程。 这个时候我们如果让自己运行的进程自己形成一个会话。 那么我们原本的会话退出是否, 就影响不到我们创建的进程了。 这个就是守护进程!
如何做到守护进程
如何让进程, 守护进程化。 这里就要用到一个函数, 叫做setsid。 创建一个新的会话设置新的进程组。
这个创建的返回值就是会话的id, 也就是sid。
然后我们创建的新的会话一定不能是进程组的组长创建。 必须是组员创建新的会话:
if(fork() > 0) exit(0); //如果是父进程(父进程一定是一个进程组的组长), 那么就直接退出, 所有子进程同属于一个进程组。这些子进程都会变成守护进程。
守护进程的本质, 其实就是孤儿进程。
接下来开始编程, 我们想要让我们上一节实现的tcp服务守护进程化。 我们这里先创建一个文件,用来定义守护进程化的函数:
cpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
#include<string>
using namespace std;
const string nullfile = "dev/null";
void Daemon(const string& cwd = "")
{
//忽略其他信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//将自己变成独立的会话
if (fork() > 0) exit(0);
setsid();
//更改当前调用进程的工作目录
if (!cwd.empty())
{
chdir(cwd.c_str());
}
//将标准输出, 标准错误进行重定向到dev/null, 垃圾文件
int fd = open(nullfile.c_str(), O_RDWR);
if (fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
首先要确定的是if(fork() > 0) exit(0)这个语句, 这个语句就是为了让左右的进程进来, 然后出去的全部都是子进程。 然后就要让所有的进程守护进程化。 另外, 也要忽略其他信号。不要因为子进程而让自己退出了, 不要让自己写数据的时候直接退出了, 不要让进程被暂停了。 更改当前调用进程的工作目录。我们知道, 我们打开一个进程, 其实就是在当前目录下工作。 但是我们的守护进程是一个对外给别人提供服务的一个进程。想要将某些数据直接写到根目录, 所以这个时候就要更改守护进程的目录到根目录。 另外我们的守护进程也会打印一些日志信息, 这些日志信息我们可以通过日志小组件实现。 但是有些信息不是利用日志打印,而是直接cerr, cout。 那么这些信息可以放到一个垃圾文件里面, 也就是dev/null, 这是系统默认提供的垃圾文件。 用来回收垃圾信息的。
然后我们在tcpserver里面调用这个函数, 启动的时候, 就能让tcp服务守护进程化:
另外, 其实还有系统默认提供的守护进程函数:
这个函数的第一个参数是nochdir, 意思就是说是否设置为零。设置为零,表示让当前进程工作在根目录下。否则就是用当前目录。 还有noclose, 如果设置就是将标准输入和标准错误重定向到dev/null。 这些功能我们上面都实现过。 这里不做赘述。
------------------以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!