PHP Swoole 守护进程开发实例:后台常驻与线程池处理

七月
七月
发布时间:2026-10-14| 6人浏览过
原创

本文讲解如何利用 PHP 的 Swoole 扩展创建高性能守护进程。内容涵盖进程隔离、PID 文件管理、线程池并发处理以及非特权用户运行等核心步骤,帮助开发者构建稳定可靠的后台服务。

为什么需要开发守护进程

守护进程是脱离于终端并在后台运行的进程。其核心价值在于:

  • 避免干扰: 不受终端信息显示或键盘输入的影响,防止意外中断。
  • 持久运行: 以服务模式存在,长期监听 TCP/UDP Socket、UNIX Socket 等接口,持续处理数据。
  • 资源隔离: 独立于前台会话,适合运行数据库同步、消息队列消费等长时间任务。

开发前准备条件

要编写一个健壮的 PHP 守护进程,需满足以下条件:

  1. Swoole 扩展: 必须安装支持多线程功能的 Swoole 版本。
  2. 依赖库: 示例中使用 ZMQ 进行通信,需安装相应的 ZeroMQ PHP 扩展。
  3. 环境权限: 为安全起见,建议以非 root 用户运行,并配置专用的 UID/GID。
  4. 冲突规避: 实现单实例运行机制,防止因重复启动导致端口冲突或资源浪费。

核心代码结构解析

完整的守护进程通常包含工作线程类、任务数据类和主控类三个部分。以下是一个基于 Swoole Pool 和 ZeroMQ 的实现示例。

<?php
class ExampleWorker extends Worker {
    // 数据库连接句柄,供子任务复用
    protected static $dbh;

    public function __construct() {}

    public function run(){
        // 在子线程中初始化数据库连接
        $dbhost = '192.168.2.1';
        $dbport = 3306;
        $dbuser = 'www';
        $dbpass = 'qwer123';
        $dbname = 'example';

        self::$dbh = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname", $dbuser, $dbpass, array(
            PDO::MYSQL_ATTR_COMPRESS => true,
            PDO::ATTR_PERSISTENT => true
        ));
    }

    protected function getInstance(){
        return self::$dbh;
    }
}

// 可收集的任务类,用于 Pool::collect 回收
/* the collectable class implements machinery for Pool::collect */
class Fee extends Stackable {
    public function __construct($msg) {
        // 解析传入的消息
        $trades = explode(",", $msg);
        $this->data = $trades;
        print_r($trades);
    }

    public function run() {
        try {
            $dbh = $this->worker->getInstance();
            
            // 执行插入操作
            $insert = "INSERT INTO fee(ticket, login, volume, `status`) VALUES(:ticket, :login, :volume,'N')";
            $sth = $dbh->prepare($insert);
            $sth->bindValue(':ticket', $this->data[0]);
            $sth->bindValue(':login', $this->data[1]);
            $sth->bindValue(':volume', $this->data[2]);
            $sth->execute();
            $sth = null;
            
            /* ...... */
            
            // 更新状态
            $update = "UPDATE fee SET `status` = 'Y' WHERE ticket = :ticket and `status` = 'N'";
            $sth = $dbh->prepare($update);
            $sth->bindValue(':ticket', $this->data[0]);
            $sth->execute();
        } catch(PDOException $e) {
            // 错误日志记录
            $error = sprintf("%s,%s\n", $mobile, $id );
            file_put_contents("mobile_error.log", $error, FILE_APPEND);
        }
    }
}

class Example {
    // 配置项
    const LISTEN = "tcp://192.168.2.15:5555";
    const MAXCONN = 100;
    const pidfile = __CLASS__;
    const uid = 80;
    const gid = 80;
    
    protected $pool = NULL;
    protected $zmq = NULL;

    public function __construct() {
        $this->pidfile = '/var/run/'.self::pidfile.'.pid';
    }

    private function daemon(){
        // 检查 PID 文件,防止重复启动
        if (file_exists($this->pidfile)) {
            echo "The file $this->pidfile exists.\n";
            exit();
        }
        
        $pid = pcntl_fork();
        if ($pid == -1) {
            die('could not fork');
        } else if ($pid) {
            // 父进程退出
            exit($pid);
        } else {
            // 子进程进入后台
            file_put_contents($this->pidfile, getmypid());
            // 降低权限至指定用户
            posix_setuid(self::uid);
            posix_setgid(self::gid);
            return(getmypid());
        }
    }

    private function start(){
        $pid = $this->daemon();
        // 创建线程池
        $this->pool = new Pool(self::MAXCONN, \ExampleWorker::class, []);
        // 绑定 ZeroMQ Socket
        $this->zmq = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REP);
        $this->zmq->bind(self::LISTEN);
        
        // 循环接收并分发任务
        while ($message = $this->zmq->recv()) {
            $this->pool->submit(new Fee($message));
            $this->zmq->send('TRUE'); 
        }
        $this->pool->shutdown(); 
    }

    private function stop(){
        if (file_exists($this->pidfile)) {
            $pid = file_get_contents($this->pidfile);
            posix_kill($pid, 9); 
            unlink($this->pidfile);
        }
    }

    private function help($proc){
        printf("%s start | stop | help \n", $proc);
    }

    public function main($argv){
        if(count($argv) < 2){
            printf("please input help parameter\n");
            exit();
        }
        if($argv[1] === 'stop'){
            $this->stop();
        }else if($argv[1] === 'start'){
            $this->start();
        }else{
            $this->help($argv[0]);
        }
    }
}

$cgse = new Example();
$cgse->main($argv);

第1步:实现后台驻留 (Fork)

目的是让程序脱离当前终端窗口,在后台持续运行。通过 pcntl_fork() 创建子进程,父进程直接退出,子进程写入 PID 文件并切换用户身份。

private function daemon(){
    // 1. 防重入检查
    if (file_exists($this->pidfile)) {
     echo "The file $this->pidfile exists.\n";
     exit();
    }
    
    $pid = pcntl_fork();
    if ($pid == -1) {
      die('could not fork');
    } else if ($pid) {
      // 父进程退出
      exit($pid);
    } else {
      // 2. 子进程执行
      // 将当前子进程的 PID 写入文件
      file_put_contents($this->pidfile, getmypid());
      // 3. 权限降级:从 root 切换到普通用户 (如 www)
      posix_setuid(self::uid);
      posix_setgid(self::gid);
      return(getmypid());
    }
}

完成判断: 执行后,当前终端无阻塞,且 /var/run/ 目录下生成了对应的 .pid 文件。

第2步:启动服务与线程池

在主进程中初始化 Swoole 线程池和 ZeroMQ 监听器,进入死循环等待数据。

  1. 初始化池: $this->pool = new Pool(self::MAXCONN, \ExampleWorker::class, []);
  2. 绑定监听: $this->zmq->bind(self::LISTEN);
  3. 任务分发: 循环调用 $this->zmq->recv() 获取消息,并通过 $this->pool->submit(new Fee($message)) 提交给线程池处理。

第3步:优雅停止服务

停止脚本需读取 PID 文件,发送信号终止进程,并清理 PID 文件。

private function stop(){
    if (file_exists($this->pidfile)) {
        $pid = file_get_contents($this->pidfile);
        // 强制杀死进程
        posix_kill($pid, 9); 
        unlink($this->pidfile);
    }
}

注意事项与排查

  • 权限问题: 若以 root 运行,子进程应主动 posix_setuid 降权,防止漏洞被利用时获得最高权限。
  • PID 文件残留: 若程序非正常退出(如断电),PID 文件可能未被删除。此时手动执行 unlink 或 rm 命令清除该文件后再启动。
  • 僵尸进程: 上述示例中父进程直接 exit,若需更严格的管理,可取消注释 pcntl_wait($status) 来回收子进程。

以上就是PHP高级编程编写守护进程的详细内容,更多关于PHP后端开发技术的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。