php 守護進程類

  最近我的項目中須要後臺運行任務,以前一直是用nouhp & + 重定向輸出 來後臺跑任務,後來以爲很差維護原始數據,同時可能也沒有直接操做進程那麼穩吧(沒驗證)。廢話少說,來看分析。php

首先,咱們守護進程的主要目的是建立一個長生存期的進程,獨立於控制端去完成你設置的任務,與此同時能夠加入事件監聽或者 狀態輪詢,構成一個完整的運行機制html


  php中使用pcntl_fork()來 建立子進程,以下程序會在父進程和子進程中分別運行,區別是返回的pid不同:
linux

$pid = pcntl_fork();
if ($pid == -1) { 建立失敗
    // 當 pid 爲 -1 的時候表示建立子進程失敗,這時返回-1。
    return false;
} else if ($pid) {//這裏運行的是父進程,可是pid 指的是他的子進程pid
    
} else { //pid 爲0 的時候,說明運行的子進程

}

    以上你須要 想象着 在不一樣的通道里去執行代碼,進程只是計算機操做系統初期提出來一個形象化的概念,包括後面更加粒度更細化的線程。api

  須要注意的是 ,你想要獨立出來完成任務,那麼就再也不依賴原來的環境了,包括目錄,上下文環境,函數

     這個時候我須要 php提供了 一些 set方法,用來設置進程的上下文環境,以下:ui

  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']);
        }
    }
相關文章
相關標籤/搜索