首页 >php教程 >PHP源码 >PHP 守护进程类

PHP 守护进程类

大家讲道理
大家讲道理原创
2016-11-08 17:37:201407浏览

用 PHP 实现的 Daemon 类。可以在服务器上实现队列或者脱离 crontab 的计划任务。 

使用的时候,继承于这个类,并重写 _doTask 方法,通过 main 初始化执行。

<?php
 
class Daemon {
 
    const DLOG_TO_CONSOLE = 1;
    const DLOG_NOTICE = 2;
    const DLOG_WARNING = 4;
    const DLOG_ERROR = 8;
    const DLOG_CRITICAL = 16;
 
    const DAPC_PATH =  &#39;/tmp/daemon_apc_keys&#39;;
 
    /**
     * User ID
     *
     * @var int
     */
    public $userID = 65534; // nobody
 
    /**
     * Group ID
     *
     * @var integer
     */
    public $groupID = 65533; // nobody
 
    /**
     * Terminate daemon when set identity failure ?
     *
     * @var bool
     * @since 1.0.3
     */
    public $requireSetIdentity = false;
 
    /**
     * Path to PID file
     *
     * @var string
     * @since 1.0.1
     */
    public $pidFileLocation = &#39;/tmp/daemon.pid&#39;;
 
    /**
     * processLocation
     * 进程信息记录目录
     *
     * @var string
     */
    public $processLocation = &#39;&#39;;
 
    /**
     * processHeartLocation
     * 进程心跳包文件
     *
     * @var string
     */
    public $processHeartLocation = &#39;&#39;;
 
    /**
     * Home path
     *
     * @var string
     * @since 1.0
     */
    public $homePath = &#39;/&#39;;
 
    /**
     * Current process ID
     *
     * @var int
     * @since 1.0
     */
    protected $_pid = 0;
 
    /**
     * Is this process a children
     *
     * @var boolean
     * @since 1.0
     */
    protected $_isChildren = false;
 
    /**
     * Is daemon running
     *
     * @var boolean
     * @since 1.0
     */
    protected $_isRunning = false;
 
    /**
     * Constructor
     *
     * @return void
     */
    public function __construct() {
 
        error_reporting(0);
        set_time_limit(0);
        ob_implicit_flush();
 
        register_shutdown_function(array(&$this, &#39;releaseDaemon&#39;));
    }
 
    /**
     * 启动进程
     *
     * @return bool
     */
    public function main() {
 
        $this->_logMessage(&#39;Starting daemon&#39;);
 
        if (!$this->_daemonize()) {
            $this->_logMessage(&#39;Could not start daemon&#39;, self::DLOG_ERROR);
 
            return false;
        }
 
        $this->_logMessage(&#39;Running...&#39;);
 
        $this->_isRunning = true;
 
        while ($this->_isRunning) {
            $this->_doTask();
        }
 
        return true;
    }
 
    /**
     * 停止进程
     *
     * @return void
     */
    public function stop() {
 
        $this->_logMessage(&#39;Stoping daemon&#39;);
 
        $this->_isRunning = false;
    }
 
    /**
     * Do task
     *
     * @return void
     */
    protected function _doTask() {
        // override this method
    }
 
    /**
     * _logMessage
     * 记录日志
     *
     * @param string 消息
     * @param integer 级别
     * @return void
     */
    protected function _logMessage($msg, $level = self::DLOG_NOTICE) {
        // override this method
    }
 
    /**
     * Daemonize
     *
     * Several rules or characteristics that most daemons possess:
     * 1) Check is daemon already running
     * 2) Fork child process
     * 3) Sets identity
     * 4) Make current process a session laeder
     * 5) Write process ID to file
     * 6) Change home path
     * 7) umask(0)
     *
     * @access private
     * @since 1.0
     * @return void
     */
    private function _daemonize() {
 
        ob_end_flush();
 
        if ($this->_isDaemonRunning()) {
            // Deamon is already running. Exiting
            return false;
        }
 
        if (!$this->_fork()) {
            // Coudn&#39;t fork. Exiting.
            return false;
        }
 
        if (!$this->_setIdentity() && $this->requireSetIdentity) {
            // Required identity set failed. Exiting
            return false;
        }
 
        if (!posix_setsid()) {
            $this->_logMessage(&#39;Could not make the current process a session leader&#39;, self::DLOG_ERROR);
 
            return false;
        }
 
        if (!$fp = fopen($this->pidFileLocation, &#39;w&#39;)) {
            $this->_logMessage(&#39;Could not write to PID file&#39;, self::DLOG_ERROR);
            return false;
        } else {
            fputs($fp, $this->_pid);
            fclose($fp);
        }
 
        // 写入监控日志
        $this->writeProcess();
 
        chdir($this->homePath);
        umask(0);
 
        declare(ticks = 1);
 
        pcntl_signal(SIGCHLD, array(&$this, &#39;sigHandler&#39;));
        pcntl_signal(SIGTERM, array(&$this, &#39;sigHandler&#39;));
        pcntl_signal(SIGUSR1, array(&$this, &#39;sigHandler&#39;));
        pcntl_signal(SIGUSR2, array(&$this, &#39;sigHandler&#39;));
 
        return true;
    }
 
    /**
     * Cheks is daemon already running
     *
     * @return bool
     */
    private function _isDaemonRunning() {
 
        $oldPid = file_get_contents($this->pidFileLocation);
 
        if ($oldPid !== false && posix_kill(trim($oldPid),0))
        {
            $this->_logMessage(&#39;Daemon already running with PID: &#39;.$oldPid, (self::DLOG_TO_CONSOLE | self::DLOG_ERROR));
 
            return true;
        }
        else
        {
            return false;
        }
    }
 
    /**
     * Forks process
     *
     * @return bool
     */
    private function _fork() {
 
        $this->_logMessage(&#39;Forking...&#39;);
 
        $pid = pcntl_fork();
 
        if ($pid == -1) {
            // 出错
            $this->_logMessage(&#39;Could not fork&#39;, self::DLOG_ERROR);
 
            return false;
        } elseif ($pid) {
            // 父进程
            $this->_logMessage(&#39;Killing parent&#39;);
 
            exit();
        } else {
            // fork的子进程
            $this->_isChildren = true;
            $this->_pid = posix_getpid();
 
            return true;
        }
    }
 
    /**
     * Sets identity of a daemon and returns result
     *
     * @return bool
     */
    private function _setIdentity() {
 
        if (!posix_setgid($this->groupID) || !posix_setuid($this->userID))
        {
            $this->_logMessage(&#39;Could not set identity&#39;, self::DLOG_WARNING);
 
            return false;
        }
        else
        {
            return true;
        }
    }
 
    /**
     * Signals handler
     *
     * @access public
     * @since 1.0
     * @return void
     */
    public function sigHandler($sigNo) {
 
        switch ($sigNo)
        {
            case SIGTERM:   // Shutdown
                $this->_logMessage(&#39;Shutdown signal&#39;);
                exit();
                break;
 
            case SIGCHLD:   // Halt
                $this->_logMessage(&#39;Halt signal&#39;);
                while (pcntl_waitpid(-1, $status, WNOHANG) > 0);
                break;
            case SIGUSR1:   // User-defined
                $this->_logMessage(&#39;User-defined signal 1&#39;);
                $this->_sigHandlerUser1();
                break;
            case SIGUSR2:   // User-defined
                $this->_logMessage(&#39;User-defined signal 2&#39;);
                $this->_sigHandlerUser2();
                break;
        }
    }
 
    /**
     * Signals handler: USR1
     *  主要用于定时清理每个进程里被缓存的域名dns解析记录
     *
     * @return void
     */
    protected function _sigHandlerUser1() {
        apc_clear_cache(&#39;user&#39;);
    }
 
    /**
     * Signals handler: USR2
     * 用于写入心跳包文件
     *
     * @return void
     */
    protected function _sigHandlerUser2() {
 
        $this->_initProcessLocation();
 
        file_put_contents($this->processHeartLocation, time());
 
        return true;
    }
 
    /**
     * Releases daemon pid file
     * This method is called on exit (destructor like)
     *
     * @return void
     */
    public function releaseDaemon() {
 
        if ($this->_isChildren && is_file($this->pidFileLocation)) {
            $this->_logMessage(&#39;Releasing daemon&#39;);
 
            unlink($this->pidFileLocation);
        }
    }
 
    /**
     * writeProcess
     * 将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程
     *
     * @return void
     */
    public function writeProcess() {
 
        // 初始化 proc
        $this->_initProcessLocation();
 
        $command = trim(implode(&#39; &#39;, $_SERVER[&#39;argv&#39;]));
 
        // 指定进程的目录
        $processDir = $this->processLocation . &#39;/&#39; . $this->_pid;
        $processCmdFile = $processDir . &#39;/cmd&#39;;
        $processPwdFile = $processDir . &#39;/pwd&#39;;
 
        // 所有进程所在的目录
        if (!is_dir($this->processLocation)) {
            mkdir($this->processLocation, 0777);
            chmod($processDir, 0777);
        }
 
        // 查询重复的进程记录
        $pDirObject = dir($this->processLocation);
        while ($pDirObject && (($pid = $pDirObject->read()) !== false)) {
            if ($pid == &#39;.&#39; || $pid == &#39;..&#39; || intval($pid) != $pid) {
                continue;
            }
 
            $pDir = $this->processLocation . &#39;/&#39; . $pid;
            $pCmdFile = $pDir . &#39;/cmd&#39;;
            $pPwdFile = $pDir . &#39;/pwd&#39;;
            $pHeartFile = $pDir . &#39;/heart&#39;;
 
            // 根据cmd检查启动相同参数的进程
            if (is_file($pCmdFile) && trim(file_get_contents($pCmdFile)) == $command) {
                unlink($pCmdFile);
                unlink($pPwdFile);
                unlink($pHeartFile);
 
                // 删目录有缓存
                usleep(1000);
 
                rmdir($pDir);
            }
        }
 
        // 新进程目录
        if (!is_dir($processDir)) {
            mkdir($processDir, 0777);
            chmod($processDir, 0777);
        }
 
        // 写入命令参数
        file_put_contents($processCmdFile, $command);
        file_put_contents($processPwdFile, $_SERVER[&#39;PWD&#39;]);
 
        // 写文件有缓存
        usleep(1000);
 
        return true;
    }
 
    /**
     * _initProcessLocation
     * 初始化
     *
     * @return void
     */
    protected function _initProcessLocation() {
 
        $this->processLocation = ROOT_PATH . &#39;/app/data/proc&#39;;
        $this->processHeartLocation = $this->processLocation . &#39;/&#39; . $this->_pid . &#39;/heart&#39;;
    }
}


声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn