PHP成为守护进程的步骤是什么,有哪些注意事项
Admin 2022-08-02 群英技术资讯 802 次浏览
今天小编跟大家讲解下有关“PHP成为守护进程的步骤是什么,有哪些注意事项”的内容 ,相信小伙伴们对这个话题应该有所关注吧,小编也收集到了相关资料,希望小伙伴们看了有所帮助。写 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 的系统中,可以保证你的守护进程不是“会话首进程”,可以阻止其重新申请获取一个控制终端。
关闭不必要的文件描述符
按照编码规范,实际还有一步是关闭不必要的文件描述符。但我们为了简单起见,上面的代码在进程启动之后先创建守护进程再执行其他操作,因此这里只打开了三个文件描述符: 0、1 和 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'); // 同上
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
今天我们一起来了解一下Laravel参数验证的相关内容,对于Laravel参数验证,有些朋友可能存在一些疑惑。例如验证器怎么创建的,谁创建的?如何自定义验证规则?如何实现用当期类方法作为验证规则验证函数?对此下文就给大家俩解答一下,感兴趣的朋友就继续往下看吧。
在前面我们了解了应该怎样去绘制图形验证码,那接下来我们一起看一看,在PHP中想要实现图像的缩放和裁剪应该怎样操作。不过在了解图像的缩放和裁剪之前我们先来认识一下下面这两个函数:
PHP数组与字符串互相转换如何实现?一些朋友可能会遇到这方面的问题,对此在下文小编向大家来讲解一下,内容详细,易于理解,希望大家阅读完这篇能有收获哦,有需要的朋友就往下看吧!
laravel竟是最慢框架,比lumen慢了一倍?就连tp都比laravel快50%?这究竟是怎么回事呢?来这里看框架性能大对比,看看比对过程。
swoole使用协程:使用go()(\Swoole\Coroutine::create() 的简写) 创建一个协程;在go() 的回调函数中, 加入协程需要执行的代码。注意是非阻塞代码。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008