什么是php守护进程
php守护进程是一种在后台持续运行的cli程序,它脱离终端控制,不会因为终端关闭而退出,通常用来处理需要长期运行的任务,比如数据同步、定时任务调度、服务监控等。和普通的cli脚本不同,守护进程运行时不依赖用户终端,生命周期独立于用户会话。

php8.5守护进程实现核心步骤
php8.5实现守护进程需要遵循Unix系统下守护进程的标准创建流程,核心步骤可以分为以下几个部分:
- 创建子进程,父进程退出,让子进程成为孤儿进程被init进程接管
- 子进程调用posix_setsid创建新的会话,脱离当前终端和进程组
- 再次创建子进程并退出父进程,避免进程重新获得控制终端
- 关闭标准输入输出流,重定向到/dev/null或者其他日志文件
- 设置信号处理函数,处理进程退出、重启等信号
- 编写核心业务逻辑循环,处理具体任务
完整实现代码示例
以下是php8.5下可用的守护进程完整实现代码,包含信号处理、日志输出、进程锁等实用功能:
<?php
// 检查是否以cli模式运行
if (php_sapi_name() !== 'cli') {
die("该脚本仅支持cli模式运行n");
}
// 检查是否安装了必要的扩展
if (!extension_loaded('posix') || !extension_loaded('pcntl')) {
die("需要安装posix和pcntl扩展n");
}
class PhpDaemon
{
// 进程pid文件路径
private string $pidFile;
// 日志文件路径
private string $logFile;
// 是否运行的标志
private bool $running = true;
public function __construct(string $pidFile, string $logFile)
{
$this->pidFile = $pidFile;
$this->logFile = $logFile;
}
// 主运行方法
public function run(): void
{
// 第一次创建子进程
$pid = pcntl_fork();
if ($pid < 0) {
die("创建子进程失败n");
} elseif ($pid > 0) {
// 父进程退出
exit(0);
}
// 子进程创建新会话,脱离终端
if (posix_setsid() === -1) {
die("创建新会话失败n");
}
// 第二次创建子进程,避免重新获得控制终端
$pid = pcntl_fork();
if ($pid < 0) {
die("第二次创建子进程失败n");
} elseif ($pid > 0) {
// 父进程退出
exit(0);
}
// 设置进程用户和组(可选,根据需求调整)
// posix_setuid(1000);
// posix_setgid(1000);
// 写入pid文件
file_put_contents($this->pidFile, posix_getpid());
// 关闭标准输入输出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 重定向输入输出到日志文件
$stdin = fopen('/dev/null', 'r');
$stdout = fopen($this->logFile, 'a');
$stderr = fopen($this->logFile, 'a');
// 设置信号处理
pcntl_async_signals(true);
pcntl_signal(SIGTERM, [$this, 'handleSignal']);
pcntl_signal(SIGINT, [$this, 'handleSignal']);
pcntl_signal(SIGHUP, [$this, 'handleSignal']);
// 记录启动日志
$this->log("守护进程启动,pid:" . posix_getpid());
// 核心业务逻辑循环
while ($this->running) {
// 这里编写具体的业务代码,比如处理队列、定时任务等
$this->log("执行任务,时间:" . date('Y-m-d H:i:s'));
// 模拟任务执行,休眠1秒
sleep(1);
}
// 清理工作
$this->log("守护进程退出");
if (file_exists($this->pidFile)) {
unlink($this->pidFile);
}
}
// 信号处理函数
private function handleSignal(int $signo): void
{
switch ($signo) {
case SIGTERM:
case SIGINT:
$this->running = false;
$this->log("收到退出信号,准备退出");
break;
case SIGHUP:
$this->log("收到重启信号,可在该方法中实现配置重载逻辑");
break;
}
}
// 日志输出方法
private function log(string $message): void
{
$log = "[" . date('Y-m-d H:i:s') . "] " . $message . "n";
file_put_contents($this->logFile, $log, FILE_APPEND);
}
// 停止守护进程
public static function stop(string $pidFile): void
{
if (!file_exists($pidFile)) {
die("pid文件不存在,守护进程可能未运行n");
}
$pid = file_get_contents($pidFile);
if (posix_kill((int)$pid, SIGTERM)) {
echo "已发送停止信号给守护进程,pid:{$pid}n";
} else {
echo "停止守护进程失败n";
}
}
}
// 脚本入口逻辑
$pidFile = __DIR__ . '/daemon.pid';
$logFile = __DIR__ . '/daemon.log';
// 判断执行命令
$command = $argv[1] ?? 'start';
switch ($command) {
case 'start':
if (file_exists($pidFile)) {
die("pid文件已存在,守护进程可能正在运行n");
}
$daemon = new PhpDaemon($pidFile, $logFile);
$daemon->run();
break;
case 'stop':
PhpDaemon::stop($pidFile);
break;
default:
echo "用法:php daemon.php [start|stop]n";
break;
}
代码使用说明
将上述代码保存为daemon.php,在cli模式下执行以下命令:
- 启动守护进程:php daemon.php start
- 停止守护进程:php daemon.php stop
启动后可以通过ps aux | grep daemon.php查看进程是否运行,日志会输出到同目录下的daemon.log文件中。
注意事项
- 运行该脚本需要php8.5开启posix和pcntl扩展,这两个扩展是处理进程和信号的基础
- 守护进程运行期间如果需要修改业务逻辑,需要停止进程后重新启动才能生效
- 避免在守护进程中执行耗时过长的阻塞操作,否则会影响其他任务的执行
- 生产环境中建议给守护进程添加监控,进程意外退出时可以自动重启
- 如果需要在守护进程中操作数据库或者文件,要注意资源释放,避免资源泄露
常见问题解答
守护进程启动后没有反应怎么办
可以先查看日志文件,确认是否有错误信息,也可以通过ps命令查看进程是否存在,如果进程不存在说明启动阶段就失败了,需要检查代码中的错误。
如何修改守护进程的运行用户
可以在创建新会话之后,调用posix_setuid和posix_setgid方法设置进程的运行用户和组,需要保证对应的用户存在且有对应文件的读写权限。
守护进程可以处理多少并发任务
php守护进程默认是单进程运行,如果需要处理高并发任务,可以在守护进程中创建多个子进程,或者使用进程池的方式实现,不过需要注意进程间的资源同步问题。