php技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > PHP编程 > php技巧 > php守护进程

php万字码出完美守护进程详解

作者:大雷编程

守护进程到底是怎么实现的?为什么有的程序既可以自己就成为守护进程,又可以通过systemd 来后台运行?本文将为大家具体讲解,感兴趣的可以了解一下

前事提要

上期我们详细学习了会话的概念以及用法,会话,进程组,终端的理解对本篇讲述的守护进程极其重要,如还不理解相关概念建议翻看我往期关于会话,进程组,终端文章。

基本概念

守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。并且不跟任何的控制终端关联,如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

常见的守护进程包括系统日志进程syslogd、 web服务器httpd、任务规划守护进程crond,数据库服务器mysqld等。一般采用以 d 结尾的名字。

查看系统守护进程命令 ps -efj

基本特点

生存周期长[非必须],一般操作系统启动的时候就启动,关闭的时候关闭。

守护进程和终端无关联,也就是他们没有控制终端,所以当控制终端退出,也不会导致守护进程退出。

守护进程是在后台运行,不会占着终端,终端可以执行其他命令

守护进程的父进程是1号进程,也就是init进程;

1 . 在命令后添加一个&符号 , 比如 php task.php & . 这个方法的缺点在于 如果terminal终端关闭 , 无论是正常关闭还是非正常关闭 , 这个php进程都会随着终端关闭而关闭 , 其次是代码中如果有echo或者print_r之类的输出文本 , 会被输出到当前的终端窗口中 .

2 . 使用nohup命令 , 比如 nohup php task.php & . 默认情况下 , 代码中echo或者print_r之类输出的文本会被输出到php代码同级目录的nohup.out文件中 . 如果你用exit命令或者关闭按钮等正常手段关闭终端 , 该进程不会被关闭 , 依然会在后台持续运行 . 但是如果终端遇到异常退出或者终止 , 该php进程也会随即退出 . 本质上 , 也并非稳定可靠的daemon方案 .

3 . 使用fork和setsid , 我暂且称之为 : *nix解决方案

创建守护进程要求

文件创建屏蔽字是指屏蔽掉文件创建时的对应位(umask() 控制系统文件和目录默认权限)。由于使用fork系统调用新建的子进程继承了父进程的文件创建掩码,这就给该子进程使用文件带来了诸多的不便。因此,把文件创建掩码设置为0,可以大大增强该守护进程的灵活性。

如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕;保证子进程不是一个进程组的组长进程,为什么要保证不是进程组组长呢? 因为进程组组长调用setsid创建会话会报错;

先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。

控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第2点的基础上,调用setsid()使进程成为会话组长:

setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。

调用setsid有3个作用:

让进程摆脱原会话的控制;

让进程摆脱原进程组的控制;

让进程摆脱原控制终端的控制

从父进程继承过来的工作目录可能在一个挂载的文件系统中。由于守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载的文件系统中,会导致该文件系统不能被卸载。

文件描述符:用来标识一个文件。当你打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符。后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数。

Linux中三个特殊的文件描述符,数字分别为0,1,2:

0:标准输入[键盘],对应的符号常量叫 STDIN_FILENO

1:标准输出[屏幕],对应的符号常量叫 STDOUT_FILENO

2:标准错误[屏幕],对应的符号常量叫STDERR_FILENO

进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。

(1)调用一次fork的作用:

第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。

(2)第二次fork的作用:

虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。

只有会话首进程能打开终端设备, 也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该守护进程不再是会话的首进程。

第二次不是必须的,是可选的。

<?php
// 1. 设置文件创建屏蔽字
umask(0);
// 2. fork 子进程
$pid = pcntl_fork();

if($pid > 0){
        print("父进程退出\n");
        exit(0);
}
//3. 设置当前子进程为会话首进程,进程组长,断开与终端的连接,成为后台进程
if(-1 === posix_setsid()){
        print("sid err \n");
}
// 4. 把守护进程工作目录设置为根目录
chdir("/");
//已经成为守护进程~\(≧▽≦)/~啦
while(1){
echo "test".PHP_EOL;
sleep(2);
}

将文件保存为daemon.php,然后php daemon.php执行文件,嗯,执行结果却有些奇怪,大概类似于下图:

即便你按Ctrl+C都没用,终端在不断输出test,唯一办法就是关闭当前终端窗口然后重新开一个,为什么会这样,这就涉及到我们上面提到的第5点,需要关闭继承过来的标准输出,输入,错误,这样我们的daemon程序不可以再将终端窗口当作默认的标准输出了。

<?php
// 设置文件创建屏蔽字
umask(0);
// 第一次fork 子进程
$pid = pcntl_fork();
if($pid > 0){
        print("父进程退出\n");
        exit(0);
}
//设置当前子进程为会话首进程,进程组长,断开与终端的连接,成为后台进程
if(-1 === posix_setsid()){
        print("sid err \n");
}
//第二次fork 彻底断开控制终端
$pid = pcntl_fork();
if($pid > 0){
	exit(0);//让会话首进程退出
}
// 把守护进程工作目录设置为根目录
chdir("/");
// 关闭标准输入,标准输出,标准错误,linux 中使用数字表示文件描述符也就是 0,1,2
fclose(STDIN);//0
fclose(STDOUT);//1
fclose(STDERR);//2
//当关掉以上标准输出,标准输入,标准错误之后,如果后面要对文件操作(比如打开一个文件,写入,创建)它返回的文件描述符从0开始,这样可能造成未知异常
//为了避免问题,我们使用输出从定向到 /dev/null 空设备文件解决这个问题,重新设置0,1,2 文件描述符用来代替标准输入,标准输出,标准错误,往 /dev/null 写入数据会被丢弃,这样就不会向终端输出数据了。
$stdin = fopen("/dev/null",'a');
$stdout = fopen("/dev/null",'a');
$stderr = fopen("/dev/null", 'a');
//已经成为守护进程~\(≧▽≦)/~啦
while(1){
echo "test".PHP_EOL;
sleep(2);
}

空设备

/dev/null : 是一个特殊的设备文件,它丢弃一切写入其中的数据(像黑洞一些)例如:echo “大雷编程” > /dev/null 输出重定向文件到黑洞(无任何输出)。

我们一般把守护进程的标准输入、标准输出重定向到空设备(黑洞),从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕。

到此这篇关于php万字码出完美守护进程详解的文章就介绍到这了,更多相关php守护进程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文