php 守护进程类

Posted 微风伏面

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了php 守护进程类相关的知识,希望对你有一定的参考价值。

  最近个人项目中需要后台运行任务,之前一直是用nouhp & + 重定向输出 来后台跑任务,后来觉得不好维护原始数据,同时可能也没有直接操作进程那么稳吧(没验证)。废话少说,来看分析。

首先,我们守护进程的主要目的是创建一个长生存期的进程,独立于控制端去完成你设置的任务,与此同时可以加入事件监听或者 状态轮询,构成一个完整的运行机制


  php中使用pcntl_fork()来 创建子进程,如下程序会在父进程和子进程中分别运行,区别是返回的pid不一样:

$pid = pcntl_fork();
if ($pid == -1) { 创建失败
    // 当 pid 为 -1 的时候表示创建子进程失败,这时返回-1。
    return false;
} else if ($pid) {//这里运行的是父进程,但是pid 指的是他的子进程pid
    
} else { //pid 为0 的时候,说明运行的子进程

}

    以上你需要 想象着 在不同的通道里去执行代码,进程只是计算机操作系统初期提出来一个形象化的概念,包括后面更加粒度更细化的线程。

  需要注意的是 ,你想要独立出来完成任务,那么就不再依赖原来的环境了,包括目录,上下文环境,

     这个时候我需要 php提供了 一些 set方法,用来设置进程的上下文环境,如下:

  posix_setsid(); chdir("/")
  可参考http://php.net/manual/zh/function.posix-setsid.php

  接着 为了让进程便于我们控制并稳定的运行,我利用了 linux系统里面的信号,通过信号来处理 相应的操作,
对于php提供的安装信号的操作函数,可以使用 pcntl_signal(),http://php.net/manual/zh/function.pcntl-signal.php
对于信号的参考可以查看一下文章:http://www.cnblogs.com/guixiaoming/p/7700132.html
使用的例子(强行停止):
posix_kill($pid, SIGKILL)


最后就是一些异常处理,以下是我的个人项目中使用的守护进程:
abstract class DaemonBase
{
    const LOG_ECHO = 1;
    const LOG_FILE = 2;

    /**
     * @var $child_pid 子进程的pid
     */
    public $child_pid;

    public $pid_file;
    public $log_file; //每个子类的日志文件


    public $switch = true; //开关

    
    public $cnt; //重启的计数

    public $do_works = []; 

    public $sig_handlers = [];

    /**
     * 配置
     */
    public static $config = [
        \'current_dir\'  => \'\',
        \'log_file\'     => \'/tmp/\' . __CLASS__ . \'.log\',
        \'pid\'          => \'/tmp/\' . \'daemon\' . \'.pid\',
        \'limit_memory\' => -1, //分配最大内存
        \'max_times\'    => 0, //重启的最大次数
    ];

    /**
     * 构造函数,设置path.以及注册shutdown
     */
    public function __construct()
    {
        $this->pid_file = \'/tmp/\' . get_class($this) . \'.pid\';
        $this->log_file = \'/tmp/\' . get_class($this) . \'.log\';

        set_error_handler([$this, \'errorHandler\']);
        register_shutdown_function(array($this, \'shutdown\'));

        ini_set(\'memory_limit\', self::$config[\'limit_memory\']);
        ini_set(\'display_errors\', \'Off\');
        clearstatcache();
    }

    /**
     * 执行启动程序
     * @param string $command
     */
    public function run($command = \'start\')
    {
        if(empty($command) || !in_array($command, [\'start\', \'stop\', \'restart\', \'status\'])){
            $command = \'help\';
        }
        $this->$command();
    }

    /**
     * 开始
     */
    public function start()
    {
        $this->log(\'Starting daemon...\', self::LOG_ECHO | self::LOG_FILE);
        $this->daemonize();

       echo \'Daemon #\' . $this->getChildPid() . \' 启动成功\' .PHP_EOL;

        declare(ticks = 1){
            while($this->switch){
                $this->autoRestart();
                $this->todo();

                try {
                    
                    $this->main();
                }catch (Exception $e) {
                    var_dump($e);
                    $this->log($e->getMessage(), self::LOG_FILE);
                }
            }
        }
    }

    /**
     * 停止
     */
    public function stop()
    {
        if (!$pid = $this->getChildPid()) {
            $this->log(\'守护进程 GG\', self::LOG_FILE);
            exit();
        }
        posix_kill($pid, SIGKILL);
    }

    protected function stopAll()
    {
        if (!is_writeable($this->log_file)) {
            $this->log(\'Daemon (no pid file) not running\', self::LOG_ECHO);
            return FALSE;
        }

        $pid = $this->getChildPid();
        unlink($this->log_file);
        $this->log(\'Daemon #\' . $pid . \' has stopped\', self::LOG_ECHO | self::LOG_FILE);
        $this->switch = TRUE;
    }

    public function restart()
    {
        if (!$pid = $this->getChildPid()) {
            $this->log(\'守护进程 GG\', self::LOG_FILE);
            exit();
        }

        posix_kill($pid, SIGHUP);
    }

    public function status()
    {
        if($pid = $this->getChildPid()) {
            $msg = "pid: $pid is running";
        }else {
            $msg = "进程GG";
        }

        $this->log($msg, self::LOG_ECHO);
    }

    /**
     * 帮助命令
     */
    public function help()
    {
        echo \'start | stop | status | restart\';
    }

    /**
     * 检测能否正常启动
     * @return bool
     */
    protected function check()
    {
        if ($pid = $this->getChildPid()) {
            $this->log("Daemon #{$pid} has already started", self::LOG_ECHO);
            return FALSE;
        }

        $dir = dirname(self::$config[\'pid\']);
        if (!is_writable($dir)) {
            $this->log("you do not have permission to write pid file @ {$dir}", self::LOG_ECHO);
            return FALSE;
        }

        if (!is_writable(self::$config[\'log_file\']) || !is_writable(dirname(self::$config[\'log_file\']))) {
            $this->log("you do not have permission to write log file: {log_file}", self::LOG_ECHO);
            return FALSE;
        }

        if (!defined(\'SIGHUP\')) { // Check for pcntl
            $this->log(\'PHP is compiled without --enable-pcntl directive\', self::LOG_ECHO | self::LOG_FILE);
            return FALSE;
        }

        if (\'cli\' !== php_sapi_name()) { // Check for CLI
            $this->log(\'You can only create daemon from the command line (CLI-mode)\', self::LOG_ECHO | self::LOG_FILE);
            return FALSE;
        }

        if (!function_exists(\'posix_getpid\')) { // Check for POSIX
            $this->log(\'PHP is compiled without --enable-posix directive\', self::LOG_ECHO | self::LOG_FILE);
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 创建子进程,并做好信号处理工作
     */
    protected function daemonize()
    {
        //检查状态
        $this->check();
        //fork 子进程
        $this->fork();

        //信号处理
        $sig_array = [
            SIGTERM => [$this, \'defaultSigHandler\'],
            SIGQUIT => [$this, \'defaultSigHandler\'],
            SIGINT  => [$this, \'defaultSigHandler\'],
            SIGHUP  => [$this, \'defaultSigHandler\'],
        ];
        foreach ($sig_array as $signo => $callback) {
            pcntl_signal($signo, $callback);
        }

       file_put_contents($this->pid_file, $this->child_pid);
    }

    /**
     * fork 子进程
     * @return bool
     */
    protected function fork()
    {
        $pid = pcntl_fork();

        if($pid == -1) { //创建子进程失败

            return false;
        }

        if($pid) { // 父进程
            exit();
        }

        //子进程
        $this->child_pid = posix_getpid(); //子进程id
        posix_setsid(); //使进程成为会话组长,让进程摆脱原会话的控制;让进程摆脱原进程组的控制;

        return true;
    }

    /**
     * 重启
     */
    protected function autoRestart()
    {
        if((self::$config[\'max_times\'] && $this->cnt >= self::$config[\'max_time\']) ||
            (0 !== self::$config[\'limit_memory\'] && memory_get_usage(TRUE) >= self::$config[\'limit_memory\']))
        {
            $this->doworks = [[$this, \'restart\']];
            $this->cnt = 0;
        }

        $this->cnt++;
    }

    public function getChildPid(){
        if(!file_exists($this->pid_file)){
            return false;
        }

        $pid = (int)file_get_contents($this->pid_file);

        return file_exists("/proc/{$pid}") ? $pid : FALSE; //检测是否确实存在此进程
    }

    public function todo()
    {

        foreach ($this->do_works as $row) {
            (1 === count($row)) ? call_user_func($row[0]) : call_user_func_array($row[0], $row[1]);
        }
    }

    /**
     * 需要执行的逻辑体
     *
     * @return mixed
     */
    abstract public function main();

    public function defaultSigHandler($signo)
    {
        switch ($signo) {
            case SIGTERM:
            case SIGQUIT:
            case SIGINT:
                $this->do_works = [[$this, \'stop\']];
                break;
            case SIGHUP:
                $this->do_works = [[$this, \'restart\']];
                break;
            default:
                break;
        }
    }

    /**
     * Regist signo handler
     *
     * @param int $sig
     * @param callback $action
     */
    public function regSigHandler($sig, $action)
    {
        $this->sig_handlers[$sig] = $action;
    }

    public function errorHandler($error_code, $msg){

    }


    /**
     * 守护进程日志
     *
     * @param string $msg
     * @param int $io, 1->just echo, 2->just write, 3->echo & write
     */
    public function log($msg, $io = self::LOG_FILE)
    {
        $datetime = date(\'Y-m-d H:i:s\');
        $msg = "[{$datetime}] {$msg}\\n";

        if ((self::LOG_ECHO & $io) && !$this->child_pid) {
            echo $msg, "\\n";
        }

        if (self::LOG_FILE & $io) {
            file_put_contents($this->log_file, $msg, FILE_APPEND | LOCK_EX);
        }
    }

    /**
     * 脚本跑完执行
     */
    public function shutdown()
    {
        if ($error = error_get_last()) {
            $this->log(implode(\'|\', $error), self::LOG_FILE);
        }

        if (is_writeable(self::$config[\'pid\']) && $this->child_pid) {
            unlink(self::$config[\'pid\']);
        }
    }

 

以上是关于php 守护进程类的主要内容,如果未能解决你的问题,请参考以下文章

php两种实现守护进程的方式

Php cli是守护进程的吗

php写守护进程(Daemon)

php程序守护进程

PHP程序守护进程化

php写守护进程(Daemon)