PHP实现守护进程的示例代码

 更新时间:2022年05月11日 15:34:18   作者:Yian  
守护进程到底是怎么实现的?为什么有的程序既可以自己就成为守护进程,又可以通过systemd 来后台运行?本文将为大家具体讲解,感兴趣的可以了解一下

前言

写 PHP CLI 程序的老司机们可能经常会写一些常驻进程,比如消息队列消费者进程,这些进程会一直运行,除非要发版,不然一般不会重启的,所以程序程序是不可能由我们通过 ssh 登录到服务器上通过终端来直接启动的(因为一旦断开 ssh 进程就退出了),常见的做法就是用 systemd 或者 supervisor 来使其成为 守护进程,这样进程就可以一直运行,遇到错误意外退出也能被自动重启。

好学的你可能会思考守护进程到底是怎么实现的?为什么有的程序既可以自己就成为守护进程,又可以通过 systemd 来后台运行?如果不依赖外部,我们的 PHP 程序该怎样变成守护进程呢?

成为守护进程的步骤

其实只需要创建子进程并退出父进程,将要处理的工作在子进程中进行就可以实现一个守护进程了。但是仅仅是这么做的话,如果后续任务很复杂,或者引入了一些第三方包,那么可能就会出现奇奇怪怪的问题了。

而在《UNIX环境高级编程》(英语:Advanced Programming in the UNIX Environment,简称APUE)一书中有介绍关于守护进程的编码规范,我们按照规范来实现我们的守护进程就可以避免出现那些奇怪的问题了。而且规范也不复杂,只需要几步就可以了:

  • 创建子进程,退出父进程
  • 子进程创建一个新的会话并成为 session leader
  • 重设文件掩码
  • 改变工作目录
  • 关闭标准输入输出

实现

<?php

function daemon()
{
    // [1] 创建子进程
    $pid = pcntl_fork();
    if ($pid == -1) {
        die('fork failed');
    }

    // [2] 如果是父进程,则退出
    if ($pid > 0) {
        exit(0);
    }

    ///////////////// 以下是子进程 /////////////////

    // [3] 创建一个新的会话并成为 session leader
    if ( ($sid = posix_setsid()) <= 0 ) {
        die("Set sid failed.\n");
    }

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

    // [5] 改变工作目录
    if (chdir('/') === false) {
        die("chdir failed.\n");
    }

    // [6] 关闭标准输入输出
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
}

daemon();

// ... 真正的处理逻辑

说明

上面短短的十几二十行代码就实现了一个守护进程,接下来解释一下有些步骤为什么要这么做。

创建子进程并退出父进程

pcntl_fork() 的返回值有三种情况,上面的代码([1]和[2])已经处理了对应的情况。

创建新的会话

调用 posix_setsid() 创建新会话会使得当前进程成为新会话中的“会话首进程”,同时也会使当前进程成为“进程组组长”,并且使得当前进程脱离控制终端。

重设文件掩码

调用 umask() 重设文件掩码,这里通常是 0。为什么是 0 而不是其他呢,因为子进程从父进程继承来的文件掩码可能会屏蔽某些特定的文件操作权限。比如说引入的第三方库可能需要用特定的权限来创建文件,并且它没有将文件权限作为一个选项参数由你指定,那么就可能会出现失败的情况;而我们传入 0,会使得从调用了 umask() 之后,守护进程创建的文件权限为 0666,目录权限为 0777,均为最高权限。

关于 umask() 后面会展开新的篇幅来说明,感兴趣的可以先自行搜索资料学习。

改变工作目录

通过 chdir() 我们将工作目录设置为根目录 /,主要是因为守护进程是长时间运行的,通常只有系统关闭/重启才会退出。假如从父进程继承来的工作目录是个挂载的文件系统,如果不改变工作目录,那么将会导致这个挂载的文件系统一直没法卸载。

当然也不一定要将工作目录切换到根目录,你也可以根据实际情况切换到特定的目录。

关闭标准输入输出

因为守护进程是脱离终端控制的,所以是没有标准输入输出交互的,我们将其关闭即可。

其他

二次 fork

你可能在一些资料中看到有人推荐你在 [3] 创建一个新的会话并成为 session leader 之后再次进行 fork。这一步骤是在基于 System V 的系统中,可以保证你的守护进程不是“会话首进程”,可以阻止其重新申请获取一个控制终端。

关闭不必要的文件描述符

按照编码规范,实际还有一步是关闭不必要的文件描述符。但我们为了简单起见,上面的代码在进程启动之后先创建守护进程再执行其他操作,因此这里只打开了三个文件描述符: 01 和 2(即标准输入、标准输出、标准错误)。

注意事项

因为上面的代码将标准输入输出关闭了,也就是说如果你在 daemon() 之后有诸如 echo "Hello world"; 之类的输出,那么你的程序将会出错然后退出,并且你将看不到任何错误信息(因为标准错误也被关闭了)。

解决方案有两种,一种是用 file_put_contents 代替 echo,但是这样并不优雅,而且万一引入的第三方包中写了 echo 或者是 file_put_contents(STDOUT, ...),那你的程序也会“莫名其妙”就挂了,会让你排查半天到底是哪里出了问题。

因此我们还可以在第[6]之后加入:

// [7] 重定向输入输出
    global $stdin, $stdout, $stderr;
    $stdin = fopen('/dev/null', 'r');
    $stdout = fopen('/dev/null', 'wb'); // 你也可以将标准输出重定向到指定的文件,相当于是日志
    $stderr = fopen('/dev/null', 'wb'); // 同上

到此这篇关于PHP实现守护进程的示例代码的文章就介绍到这了,更多相关PHP守护进程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • php魔术变量用法实例详解

    php魔术变量用法实例详解

    这篇文章主要介绍了php魔术变量用法,以实例形式详细分析了php中常见的各种魔术变量的具体用法,并补充说明了php中的魔术方法,具有不错的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • PHP从尾到头打印链表实例讲解

    PHP从尾到头打印链表实例讲解

    这篇文章给大家分享了关于PHP从尾到头打印链表的相关知识点内容,有需要的朋友们可以测试下。
    2018-09-09
  • PHP syntax error, unexpected $end 错误的一种原因及解决

    PHP syntax error, unexpected $end 错误的一种原因及解决

    PHP 遇到 syntax error, unexpected $end 错误时,查错思路其实还是看看文件里 PHP 的开始标记和结束标记是否配对,还要额外注意注释里是否出现过 ?> 哟。
    2008-10-10
  • PHP多线程类及用法实例

    PHP多线程类及用法实例

    这篇文章主要介绍了PHP多线程类及用法,实例分析了多线程类的具体实现方法及应用技巧,并结合下载远程图片的实例予以深入分析,需要的朋友可以参考下
    2014-12-12
  • PHP的array_diff()函数在处理大数组时的效率问题

    PHP的array_diff()函数在处理大数组时的效率问题

    PHP 5.2.6 以上版本的 array_diff() 函数在处理大数组时,需要花费超长时间,这个 bug 已经被官方确认;在这个问题被修复之前或者在我们不能控制 PHP 版本的时候,可以使用本文提供的方法
    2011-11-11
  • PHP中功能强大却很少使用的函数实例小结

    PHP中功能强大却很少使用的函数实例小结

    这篇文章主要介绍了PHP中功能强大却很少使用的函数,结合实例形式总结分析了php中非常实用的几个函数,包括函数的调用、注册、调用、判断等操作技巧,需要的朋友可以参考下
    2016-11-11
  • php foreach循环中使用引用的问题

    php foreach循环中使用引用的问题

    本文来给大家介绍php foreach循环中使用引用问题分析与注意事项,希望此文章对各位同学会有所帮助
    2013-11-11
  • PHP 获取远程文件内容的函数代码

    PHP 获取远程文件内容的函数代码

    PHP 获取远程文件内容的代码,后面有一些注释可以参考下,其实大家可以参考脚本之家发布的一些采集程序代码。
    2010-03-03
  • PHP与SQL注入攻击[一]

    PHP与SQL注入攻击[一]

    PHP与SQL注入攻击[一]...
    2007-04-04
  • header与缓冲区之间的深层次分析

    header与缓冲区之间的深层次分析

    实际的开发中,大家是否听说过在header之前不能有任何的实际输出。甚至有的认为header函数必须写在代码的最前面。可是你是否试验过header函数之前输出东西?下来让我们更深层次的了解一下
    2016-07-07

最新评论