xshell原理
可能我们都在使用xshell时,都会遇到一些问题,就是你在xshell运行了你的服务器。可是你把xshell页面一关,你的服务器就自动关闭了,这是为什么呢??
本质是因为我们的xshell在登陆服务器时,会创建一个会话,而在这个会话中,只能允许一个进程在前台运行,多个进程在后台运行。而会话退出时会话的内容也会跟着退出,因为会话的内容都是以bash为父进程创建的。我们在命令行输入执行的进程,都是以bash为父进程创建的。
我们还要明白一个进程组的概念,我们可以分别执行 sleep 10000 | sleep 20000 | sleep 30000 &
和 sleep 40000 | sleep 50000 | sleep 60000 &
命令,会建立6个睡眠的进程,而&的意思是在后台中运行。然后我们输入jobs,可以发现有2个进程组。
我们在查看进程,会发现有2个进程组,且进程组id和进程id相同的进程即为进程组组长。
而这个2个进程组组长都是由bash创建的。举个例子:
bash 就是大老板,它给了A一大笔钱,让A自己找几个人帮他完成一些任务。随后A就找了自己的兄弟一起给大老板bash干活。然后bash又找到B,给了B一大笔钱,让B自己组个队伍去帮他完成另一个任务。随后B也拉上了自己的兄弟们一起为大老板bash干活。
这里的大老板就是bash,A就是进程组组长,B是另一个进程组的组长。而它们都是为bash干活,而一旦bash退出,那么它们也自然会跟着退出。因为老板都挂了,进程组们肯定原地解散了。
而我们发现当命令行处于前台状态在运行进程时,就无法在使用命令行了。这本质的原因就是因为一个会话最多只允许一个进程在前台运行。
那么我们怎么让A和B不受会话的限制,即使会话退出了也不会影响它们运行呢?
很简单,我们让进程组组长不要替别人干活啦!自己出去单干,那么自己就是老大!让它们自己出去自成终端!自成进程组!自成会话!除非用户自己关闭或者操作系统挂了,那么它们就不在受到会话的影响。即使会话退出,它们依旧可以运行。这种进程我们把它称为守护进程 ,也可以叫做精灵进程 ,本质上还是一个孤儿进程
需要注意的是,想要自己出去单干,那么自己必须当老大。也就是说自己必须成为进程组的组长。
创建守护进程
上面我说过,守护进程的本质还是个孤儿进程,那么就意味着它必定会被操作系统领养。
实现守护进程我们可以分三个重要步骤,若干个小步骤。
1. 让调用的进程忽略掉异常信号
比如SIGPIPE信号,如果现在我要把服务器自身独立成为会话。那么要忽略掉这个信号,避免客户端搞事(在服务器读取时客户端关闭,服务器会收到SIGPIPE信号)。
2. 自成进程组
有一个setsid函数,可以让自己自成进程组,并自身处于新会话内,也就是说脱离了bash的掌控之中了!但是有个条件!那就是调用的进程本身不能是进程组! ,否则会调用失败!只要fork一下,那么fork出来的子进程就绝对部署进程组组长。这时候再把父进程立马退出,让子进程执行setsid。即可让子进程脱离bash的魔掌!
该函数调用成功返回进程 的pid,调用失败返回-1.
#include <unistd.h>
pid_t setsid(void);
手册对函数的说明:
3 .关闭或重定向进程默认打开的文件
如果我的服务器有大量的输出内容,如果不关闭默认打开的文件的话。那么可能会占用系统的IO资源。而在/dev/null
路径的null文件。是一个“黑洞” ,无论你往里面读还是写。都不会发生什么,可以认为是个垃圾桶,但是在垃圾桶里面还能捡到东西,而你这里面你读不到消息,写进去的消息也没有反应,所以我愿称之为"黑洞"。 我们可以直接关闭标准输入输出错误,但不建议,建议还是重定向到 /dev/null中。
**4. 进程的执行路径发生更改(非必须) **
我们都知道进程所在的目录路径是会保存在cwd中的,所以程序里 open一个文件如果不用绝对路径,那么自动从当前路径开始找,这是因为当前路径其实已经被进程保存在cwd中了。如果想要修改也是可以的,但是这并不是必须的。
接下来我们用代码来实现一下:
#pragma once
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV "/dev/null"
void daemonSelf(const char* currpath = nullptr)
{
//1.屏蔽信号
signal(SIGPIPE,SIG_IGN);
//2.自成进程组
if(fork() > 0 ) exit(0); //父进程秒退
//子进程自成进程组
pid_t ret = setsid();
assert(ret != -1);
//3.关闭默认打开的文件
int fd = open(DEV,O_RDWR);
if(fd < 0)
{
//文件打开失败,那么就关闭标准输入/输出/错误
close(0);
close(1);
close(2);
}else{
//替换掉标准输入输出错误
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
}
//4. 路径修改
if(currpath) chdir(currpath);
close(fd);
}
那么接下来我们写个程序验证一下。
#include "deamon.hpp"
#include <iostream>
#include <string>
#include <unistd.h>
int main()
{
daemonSelf(); //独立进程组
FILE* testTxt = fopen("test.txt","a");
int i = 0;
while(1)
{
std::string msg = "hello" + std::to_string(i);
fprintf(testTxt,msg.c_str());
sleep(1);
}
return 0 ;
}
我们会发现进程一执行就结束了,可是我们明明写的是死循环啊。
这时候我们关掉vscode终端,打开另一个终端,查看test进程。
我们发现test进程已经脱离bash掌控了,也就意味着我们的程序可以24小时在服务器上运行了!