PHP Socket相关01 - 守护进程、多进程

0)前言

针对这个篇幅主要是记录一些坑,列出的只是我遇到过的但并不一定是最好的方案

守护进程

一搜一大堆的知识点,不扯太多,简单说就是后台运行且不受任何终端控制的进程,终端里造成一个已经运行完毕的假象,后续所有的工作都由子进程完成

大体分四步:

1.fork子进程终止父进程
为毛要终止父进程呢,首先最直观的表现就是,开守护进程基本就是为了处理一些阻塞、耗时的任务,不终止的话父进程就会参与后续的操作造成阻塞住,终止父进程时终端认为命令已执行完毕,造成运行完毕的假象。其次才是重点需要结合第二点往下看

2.创建新的Session(会话)并提升子进程为这个Seesion的组长
fork子进程,子进程将会继承父进程的Seesion,虽然父进程被终止了但子进程依然使用父进程的Seesion,并没有真正的独立。为了让进程摆脱原会话的控制,所以需要使用posix_setsid函数(如想查看相关资料可直接搜索linux的setsid函数资料比较多),创建新的Seesion,并将子进程提升为该Seesion的组长。setsid函数无法对组长进程进行设置所以就扯回第一点的需要终止父进程。

补充一下,最初我认为如果不使用setsid函数,当会话关闭时就会导致守护进程执行终止,其实不然,使用setsid函数只是为了脱离控制终端,让进程摆脱原会话的控制不受影响,具体的引发的错误后续有发现再进行补充..

3.再fork一次...
正常fork一次也就够了,就是严谨一些,防止误操作打开新的终端

代码贴在文章下方,为了不写太长就只贴一份,区别有注释

ProcessDemo1为不使用setsid函数且未fork两次
ProcessDemo2为使用setsid函数且未fork两次
ProcessDemo3不使用setsid函数且fork两次

执行此命令对比一下进程情况

ps axj | grep php
父进程id   进程id    进程组id   会话id    终端
PPID     PID         PGID      SID      TTY
1        2557        2555      1976     pts/1     1976 S        0   0:00 php ProcessDemo1.php
1        2559        2555      1976     pts/1     1976 S        0   0:00 php ProcessDemo1.php
1        2562        2562      2562     ?           -1 Ss       0   0:00 php ProcessDemo2.php
1        2564        2564      2564     ?           -1 Ss       0   0:00 php ProcessDemo2.php
1        2568        2566      2566     ?           -1 S        0   0:00 php ProcessDemo3.php
1        2570        2569      2569     ?           -1 S        0   0:00 php ProcessDemo3.php


简单分析一下

1.PGID与PID一致时,此进程就为进程组Leader
2.SID与PID一致时,此进程就为Seesion Leader
3.调用setsid函数时该进程有控制终端,那么与该终端的联系被解除,所以TTY为?
4.可以看到上面6个进程的PPID都为1,这是因为终止了父进程后子进程成为孤儿进程被init进程接管了,init进程为1,init进程由idle进程创建,idle是系统创建的第一个进程为0

守护进程特点自成进程组、会话,和终端无关

(1)3、4、5、6的TTY为?,所以是设置了setsid的守护进程,1、2则不满足,1、2和3、4的差别只有没调用setsid函数的区别,所有3、4原先也是属于某个终端的,说明setsid函数会将有终端归属的进程设置成无归属的
(2)3、4的 PID、PGID、SID 都一致说明此进程为进程组Leader、Seesion Leader,守护进程最重要的一点是摆脱终端的干预,也就是终端退出了它也能继续运行

4.重置从父进程继承下来的资源
例如
1.重置掩码值
这段有个文章写得很不错直接copy了..
子进程从父进程那里继承了文件创建掩模码值,为避免父进程继承来的文件掩码值影响守护进程的执行,可通过umask(0)可以将文件掩模清除,调用umask把文件模式创建屏蔽字 设置为 0,由于 umask 接收的参数会被取反,所以这个 0 传进去取反以后是最大的,也就是给该程序最大的权限。如果应用程序根本就不涉及创建新文件或是文件访问权限的限定,这一步不是必须的。

2.重置工作目录正常会重置到根目录
防止用户改动目录,从而影响进程的运行,例如在一个临时/挂载目录执行的,后续临时/挂载目录被用户删除弃用后会导致守护进程执行异常

3.重置标准输入输出/错误输出的位置
子进程会继承父进程的标准输入输出,父进程的标准输入输出是通过执行的终端的,关闭了终端后,守护进程中的子进程无法获取/输出被关闭的终端,进而导致守护进程执行异常

附上代码:

 0) {
    exit(' parent process. ');
}
// 将当前子进程提升会会话组组长 这是至关重要的一步
//主要目的脱离终端控制,自立门户。
//创建一个新的会话,而且让这个pid统治这个会话,他既是会话组长,也是进程组长。
//而且谁也没法控制这个会话,除了这个pid。当然关机除外。。
//这时可以成做pid为这个无终端的会话组长。
//注意这个函数需要当前进程不是父进程,或者说不是会话组长。
//在这里当然不是,因为父进程已经被kill
//↓↓↓ProcessDemo1没有这段代码↓↓↓
if (!posix_setsid()) {
    exit(' setsid error. ');
}
//↑↑↑ProcessDemo1没有这段代码↑↑↑

// 二次fork
//↓↓↓ProcessDemo1、ProcessDemo2没有这段代码↓↓↓
$pid = pcntl_fork();
if ($pid < 0) {
    exit(' fork error. ');
} else if ($pid > 0) {
    exit(' parent process. ');
}
//↑↑↑ProcessDemo1、ProcessDemo2没有这段代码↑↑↑

//重设文件掩码
umask(0);

//↓↓↓标准输入输出操作↓↓↓
global $STDOUT, $STDERR, $STDIN;
fclose(STDOUT);
fclose(STDERR);
fclose(STDIN);
//如有输出需求可指定输出对象,设置为a已追加形式输出
$fd = fopen('/data/www/PHPSocket/log.txt', "a");
$STDOUT = $fd;
$STDERR = $fd;
//标准输入守护进程肯定用不到的直接指向一个空设备
$STDIN = '/dev/null';
//↑↑↑标准输入输出操作↑↑↑

//设置工作目录到根目录
chdir( '/' );

//模拟任务代码
for ($i = 1; $i <= 100; $i++) {
    sleep(1);
    //echo 等输出有可能导致守护进程终端
    //当关闭当前终端时标准输出指向的终端输出设备已被关闭导致无法输出进而终端程序
    //所以需要提前设置标准输出关闭或指向指定输出位置
    echo "$i \n";
    file_put_contents(__DIR__ . '/process_demo_1.log', $i, FILE_APPEND);
}


echo "进程结束pid:[" . posix_getpid() . "]\n";

你可能感兴趣的:(PHP Socket相关01 - 守护进程、多进程)