PHP脚本守护进程原理与实现方法详解
背景
要想实现持续运行某个 PHP 程序,一般常见的方式是在终端执行 php your-script.php
命令,但是这种方式不够优雅,因为无法后台运行,当终端连接关闭时,该 PHP 程序也会随之退出。而守护进程是一种让后台程序持续运行的方式,我们可以借助它来实现 PHP 脚本目标。
原理
守护进程是指一种在操作系统后台长时间运行的进程,它的存在是为了不间断的执行特定任务。它一般父进程完成以下任务:
- 创建一个子进程并立即退出,让子进程称为孤儿进程;
- 在子进程中调用 setsid() 函数创建新的会话,成为 session leader 且不再有控制终端器;
- 再一次创建一个新的子进程,使用 exec() 函数执行真正的代码;
- 子进程开始执行,成为渐消进程,就可以长时间运行了。
实现
下面提供基于 PHP 实现守护进程的方法,以 CLI 模式执行 Script 的信息将会记录到 daemon.log
文件中。
#!/usr/bin/env php
<?php
$log_file = __DIR__ . '/daemon.log';
$fp = stream_socket_server('tcp://0.0.0.0:8888', $errno, $errstr);
if (!$fp) {
exit("Create socket failed: $errstr ($errno)" . PHP_EOL);
}
// 创建子进程
$pid = pcntl_fork();
if ($pid === -1) {
exit("Fork child process failed!" . PHP_EOL);
} else if ($pid > 0) {
exit(0);
}
// 当前进程成为会话领导
if (posix_setsid() === -1) {
exit("Child process create session failed!" . PHP_EOL);
}
// 关闭不必要的文件描述符
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 重定向标准 IO 流
$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen($log_file, 'ab');
$STDERR = fopen($log_file, 'ab');
// 再次创建子进程,禁止进程打开控制终端
$pid = pcntl_fork();
if ($pid === -1) {
exit("Fork child process failed!" . PHP_EOL);
} else if ($pid > 0) {
exit(0);
}
// 保存当前进程 ID
if (!file_put_contents(__DIR__ . '/daemon.pid', posix_getpid())) {
exit("Failed to save pid file!" . PHP_EOL);
}
while (true) {
// 接收客户端连接
$client = stream_socket_accept($fp);
// 处理请求
fclose($client);
}
以上代码会创建守护进程并监听 8888 端口,收到客户端连接后处理请求并关闭连接。需要注意的是,在本代码示例中:
- 我们创建了两次子进程,原因是第一次子进程将 session leader 的权限剥夺掉了,以防发生意外情况。
- 为了避免在后台运行程序时因为 STDIN、STDOUT、STDERR 等文件描述符被关闭而导致崩溃问题,需要将其重定向到
/dev/null
或日志文件等。
示例
以下是一个示例,该示例通过网络发起一个 HTTP 请求,模拟客户端链接,并向 localhost:8888
发送 hello
消息。如果后台程序正常启动,该示例将会得到响应。
<?php
$fp = fsockopen('localhost', 8888, $errno, $errstr, 1);
if (!$fp) {
echo "$errstr ($errno)" . PHP_EOL;
} else {
$out = "hello\n";
fwrite($fp, $out);
echo fread($fp, 8192);
fclose($fp);
}
结语
通过上述方法,我们可以使用 PHP 实现守护进程,让程序在后台仍然运行,这种方式有助于提升服务稳定性,完全可以应用在多种实际场景中。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:php脚本守护进程原理与实现方法详解 - Python技术站