搜索
首页后端开发php教程PHP实现系统编程之 编写守护进程详解

本篇文章给大家分享的内容是PHP实现系统编程之 编写守护进程详解,有着一定的参考价值,有需要的朋友可以参考一下

(一)进程组、会话、控制终端、控制进程等概念

进程组:每个进程都有一个所属的进程组 (process group),进程组有一个进程组长(process group leader),进程组ID即为这个进程组长的进程号,所以判断一个进程是否为进程组组长,只需比较该进称号是否和它的进程组id相等即可,PHP中可以用函数 posix_getpgrp() 获取当前进程的进程组id,用 posix_getpid() 获取当前进程的进程号。


<?php

function isGroupLeader() {
    return posix_getpgrp() == posix_getpid();
}

$pid = pcntl_fork();

if($pid == 0) {
    echo &#39;子进程:&#39; . PHP_EOL;
}
elseif($pid > 0) {
    sleep(2);
    echo &#39;父进程:&#39; . PHP_EOL;
}

echo "当前进程组gid:" . posix_getpgrp() . PHP_EOL;
echo "当前进程号pid:" . posix_getpid() . PHP_EOL;

if (isGroupLeader()) {
    echo &#39;Is a process group leader&#39; . PHP_EOL;
}
else {
    echo &#39;Is not a process group leader&#39; . PHP_EOL;
}

以上例程会输出:



子进程:
当前进程组gid:15827
当前进程号pid:15828
Is not a process group leader
父进程:
当前进程组gid:15827
当前进程号pid:15827
Is a process group leader

会话:会话(session)是若干进程组的集合,会话中的一个进程组为会话组长(session leader),会话ID即为这个会话组长的进程组id,PHP中可以使用函数 posix_getsid(int $pid)  来获取指定进程的会话id,也可以使用函数 posix_setsid() 来创建一个新的会话,此时该进程成为新会话的会话组长,该函数调用成功返回新创建的会话ID,或者在失败出错时返回-1,注意linux中调用 posix_setsid() 函数的进程不能是进程组长,否则会调用失败,这是由于一个进程组中的进程不能同时跨多个会话


linux 中关于setsid的文档介绍:


setsid()  creates  a  new  session  if  the calling process is not a process group leader.  The calling process is the leader of the new session, the process group
       leader of the new process group, and has no controlling tty.  The process group ID and session ID of the calling process are set to the PID of the calling process.
       The calling process will be the only process in this new process group and in this new session.
<?php

function isGroupLeader() {
    return posix_getpgrp() == posix_getpid();
}

echo "当前会话id: " . posix_getsid(0) . PHP_EOL; //传0表示获取当前进程的会话id

if (isGroupLeader()) {
    echo "当前进程是进程组长\n";
}

$ret = posix_setsid();  //创建一个新会话
var_dump($ret);  //由于当前进程是进程组长,此处会返回-1, 表示调用失败

以上例程会输出:



当前会话id: 13000
当前进程是进程组长
int(-1)

那该如何新建会话呢,我们注意到前面使用pcntl_fork() 创建了一个子进程后,这个子进程就不是进程组长,所以可以利用子进程来创建新会话。



<?php
function isGroupLeader()
{
     return posix_getpgrp() == posix_getpid();
}
echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL;

$pid = pcntl_fork();

if ($pid > 0) {
    exit(0); // 让父进程退出
}
elseif ($pid == 0) {
    if (isGroupLeader()) {
        echo "是进程组组长\n";
    } else {
        echo "不是进程组组长\n";
    }
    echo "进程组ID:" . posix_getpgrp() . PHP_EOL;  
    echo "进程号pid: " . posix_getpid() . PHP_EOL;

    $ret = posix_setsid();
    var_dump($ret);

    echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL;
}

以上例程会输出:



当前进程所属会话ID:13000
[root@localhost php]# 不是进程组组长
进程组ID:15856
进程号pid: 15857
int(15857)
当前进程所属会话ID:15857

利用子进程成功创建了新的会话。

控制终端控制进程:(终端是所有输入输出设备的总称,比如键盘,鼠标,显示器都是一个终端)一个会话可以有一个控制终端,一个控制终端被一个会话独占。会话刚创建的时候是没有控制终端的,但会话组长可以申请打开一个终端,如果这个终端不是其他会话的控制终端,这时的终端将会成为会话的控制终端,会话组长叫做控制进程。


linux下判断一个会话是否拥有控制终端,我们可以尝试打开一个特殊的文件 /dev/tty , 他指向了真实的控制终端,如果打开成功说明拥有控制终端,反之则没有控制终端。


<?php
function isGroupLeader()
{
     return posix_getpgrp() == posix_getpid();
}

$pid = pcntl_fork();

if ($pid > 0) {
        sleep(1);
        $fp = fopen("/dev/tty", "rb");
        if ($fp) {
            echo "父进程会话 " . posix_getsid(0) . " 拥有控制终端\n";
        } else {
            echo "父进程会话 " . posix_getsid(0) . " 不拥有控制终端\n";
        }

    exit(0); // 让父进程退出
}
elseif ($pid == 0) {
    if (isGroupLeader()) {
        echo "是进程组组长\n";
    } else {
        echo "不是进程组组长\n";
    }

    $ret = posix_setsid();
    var_dump($ret);

    $fp = fopen("/dev/tty", "rb");
    if ($fp) {
            echo "子进程会话 " . posix_getsid(0) . " 拥有控制终端\n";
    }   else {
            echo "子进程会话 " . posix_getsid(0) . " 不拥有控制终端\n";
    }
}

上述例程子进程新建了一个会话,然后父子进程都尝试打开文件 /dev/tty,例程输出如下:



不是进程组组长
int(15906)
PHP Warning:  fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30

Warning: fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30
子进程会话 15906 不拥有控制终端
父进程会话 13000 拥有控制终端

产生SIGHUP信号

1、当一个会话失去控制终端时,内核会向该会话的控制进程发送一个 SIGHUP 信号,而通常会话的控制进程是shell进程,shell在收到一个 SIGHUP 信号时,会向由它创建的所有进程组(前台或后台进程组)也发送一个SIGHUP信号,然后退出,进程收到一个SIGHUP信号的默认处理方式就是退出进程,当然进程也可以自定义信号处理或者忽略它。

2、另外,当控制进程终止时,内核也会向终端的前台进程组的所有成员发送SIGHUP信号。


<?php
$callback = function($signo){
        $sigstr = &#39;unkown signal&#39;;
        switch($signo) {
        case SIGINT:
            $sigstr = &#39;SIGINT&#39;;
            break;
        case SIGHUP:
            $sigstr = &#39;SIGHUP&#39;;
            break;
        case SIGTSTP:
            $sigstr = &#39;SIGTSTP&#39;;
            break;
        }
       file_put_contents("daemon.txt", "catch signal $sigstr\n", FILE_APPEND);
};

pcntl_signal(SIGINT, $callback);
pcntl_signal(SIGHUP, $callback);
pcntl_signal(SIGTSTP, $callback);

while(1)
{
    sleep(100);
    pcntl_signal_dispatch();
}

使用 php sighup.php运行起该程序,然后直接关掉终端,重新登录shell,会发现该程序仍在运行,daemon.txt 文件中会 记录捕获到的SIGHUP信号。


[root@localhost php]# cat daemon.txt 
catch signal SIGHUP
[root@localhost php]# ps aux | grep sighup.php 
root     18438  0.0  0.4 191600  8996 ?        S    16:48   0:00 php sighup.php
root     18443  0.0  0.0 103328   896 pts/0    S+   16:53   0:00 grep sighup.php

同时linux下提供了一个nohup命令,可以让进程忽略所有的SIGHUP信号,例如


[root@localhost php]# nohup php sighup.php 
nohup: 忽略输入并把输出追加到"nohup.out"


(二)标准输入、标准输出、标准错误输出

php中有三个默认打开的文件句柄 STDIN,STDOUT, STDERR 分别对应上述三个文件描述符,而由于标准输入输出是和终端相关的,对于守护进程来说并没有什么用,可以直接关闭,但是直接关闭可能会造成一个问题,请看下面这段代码


<?php
fclose(STDOUT);

$fp = fopen("stdout.log", "a");

echo "hello world\n";

运行上述代码时,屏幕不会输出echo的信息,而是写入到打开的文件中了,这是由于关闭STDOUT文件句柄后,释放了对应的文件描述符,而linux打开文件总是使用最小的可用文件描述符,所以这个文件描述符现在指向fopen打开的文件了,导致原本写到标准输出的信息现在写到了文件里。为了避免这种怪异的行为,我们在关闭这三个文件句柄之后可以立即打开 linux提供的黑洞文件 /dev/null,比如:


<?php
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

$fp = fopen("stdout.log", "a");

echo "hello world\n";

上面这个例程关闭STDIN,STDOUT, STDERR立马打开 /dev/null 三次,这样echo的信息会直接写到黑洞中,避免了前面出现的怪异的问题。


(三)编写守护进程涉及的其他问题

编写守护进程还涉及工作目录、文件掩码、信号处理、热更新、安全的启动停止等等问题,这里先留给大家自己百度,后期有空再来补充。


(四)一个守护进程的示例

<?php

//由于进程组长无法创建会话,fork一个子进程并让父进程退出,以便可以创建新会话
switch(pcntl_fork()) {
    case -1:
            exit("fork error");
            break;
    case 0: 
            break;
    default:
            exit(0); //父进程退出
}

posix_setsid();  //创建新会话,脱离原来的控制终端

//再次fork并让父进程退出, 子进程不再是会话首进程,让其永远无法打开一个控制终端
switch(pcntl_fork()) {
    case -1:
        exit("fork error");
        break;
    case 0:
        break;
    default:
        exit(0); //父进程退出
}

//关闭标准输入输出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

//切换工作目录
chdir(&#39;/&#39;);

//清除文件掩码
umask(0);

//由于内核不会再为进程产生SIGHUP信号,我们可以使用该信号来实现热重启
pcntl_signal(SIGHUP, function($signo){
    //重新加载配置文件,重新打开日志文件等等
});

for(;;)
{
     pcntl_signal_dispatch();  //处理信号回调
    //实现业务逻辑
}



to be continue!

相关推荐:

PHP实现系统编程之网络Socket及IO多路复用

PHP实现系统编程之本地套接字(Unix Domain Socket)

PHP实现系统编程之 多进程编程介绍及孤儿进程、僵尸进程




以上是PHP实现系统编程之 编写守护进程详解的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
您如何修改PHP会话中存储的数据?您如何修改PHP会话中存储的数据?Apr 27, 2025 am 12:23 AM

tomodifyDataNaphPsession,startTheSessionWithSession_start(),然后使用$ _sessionToset,修改,orremovevariables.1)startThesession.2)setthesession.2)使用$ _session.3)setormodifysessessvariables.3)emovervariableswithunset()

举一个在PHP会话中存储数组的示例。举一个在PHP会话中存储数组的示例。Apr 27, 2025 am 12:20 AM

在PHP会话中可以存储数组。1.启动会话,使用session_start()。2.创建数组并存储在$_SESSION中。3.通过$_SESSION检索数组。4.优化会话数据以提升性能。

垃圾收集如何用于PHP会议?垃圾收集如何用于PHP会议?Apr 27, 2025 am 12:19 AM

PHP会话垃圾回收通过概率机制触发,清理过期会话数据。1)配置文件中设置触发概率和会话生命周期;2)可使用cron任务优化高负载应用;3)需平衡垃圾回收频率与性能,避免数据丢失。

如何在PHP中跟踪会话活动?如何在PHP中跟踪会话活动?Apr 27, 2025 am 12:10 AM

PHP中追踪用户会话活动通过会话管理实现。1)使用session_start()启动会话。2)通过$_SESSION数组存储和访问数据。3)调用session_destroy()结束会话。会话追踪用于用户行为分析、安全监控和性能优化。

如何使用数据库存储PHP会话数据?如何使用数据库存储PHP会话数据?Apr 27, 2025 am 12:02 AM

利用数据库存储PHP会话数据可以提高性能和可扩展性。1)配置MySQL存储会话数据:在php.ini或PHP代码中设置会话处理器。2)实现自定义会话处理器:定义open、close、read、write等函数与数据库交互。3)优化和最佳实践:使用索引、缓存、数据压缩和分布式存储来提升性能。

简单地说明PHP会话的概念。简单地说明PHP会话的概念。Apr 26, 2025 am 12:09 AM

phpsessionstrackuserdataacrossmultiplepagerequestsusingauniqueIdStoredInacookie.here'showtomanageThemeffectionaly:1)startAsessionWithSessionwwithSession_start()和stordoredAtain $ _session.2)

您如何循环中存储在PHP会话中的所有值?您如何循环中存储在PHP会话中的所有值?Apr 26, 2025 am 12:06 AM

在PHP中,遍历会话数据可以通过以下步骤实现:1.使用session_start()启动会话。2.通过foreach循环遍历$_SESSION数组中的所有键值对。3.处理复杂数据结构时,使用is_array()或is_object()函数,并用print_r()输出详细信息。4.优化遍历时,可采用分页处理,避免一次性处理大量数据。这将帮助你在实际项目中更有效地管理和使用PHP会话数据。

说明如何使用会话进行用户身份验证。说明如何使用会话进行用户身份验证。Apr 26, 2025 am 12:04 AM

会话通过服务器端的状态管理机制实现用户认证。1)会话创建并生成唯一ID,2)ID通过cookies传递,3)服务器存储并通过ID访问会话数据,4)实现用户认证和状态管理,提升应用安全性和用户体验。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具