欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

PHP 守护进程类

程序员文章站 2022-03-08 13:40:03
...
用 PHP 实现的 Daemon 类。可以在服务器上实现队列或者脱离 crontab 的计划任务。

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

_logMessage('Starting daemon');
 
        if (!$this->_daemonize()) {
            $this->_logMessage('Could not start daemon', self::DLOG_ERROR);
 
            return false;
        }
 
        $this->_logMessage('Running...');
 
        $this->_isRunning = true;
 
        while ($this->_isRunning) {
            $this->_doTask();
        }
 
        return true;
    }
 
    /**
     * 停止进程
     *
     * @return void
     */
    public function stop() {
 
        $this->_logMessage('Stoping daemon');
 
        $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't fork. Exiting.
            return false;
        }
 
        if (!$this->_setIdentity() && $this->requireSetIdentity) {
            // Required identity set failed. Exiting
            return false;
        }
 
        if (!posix_setsid()) {
            $this->_logMessage('Could not make the current process a session leader', self::DLOG_ERROR);
 
            return false;
        }
 
        if (!$fp = fopen($this->pidFileLocation, 'w')) {
            $this->_logMessage('Could not write to PID file', 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, 'sigHandler'));
        pcntl_signal(SIGTERM, array(&$this, 'sigHandler'));
        pcntl_signal(SIGUSR1, array(&$this, 'sigHandler'));
        pcntl_signal(SIGUSR2, array(&$this, 'sigHandler'));
 
        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('Daemon already running with PID: '.$oldPid, (self::DLOG_TO_CONSOLE | self::DLOG_ERROR));
 
            return true;
        }
        else
        {
            return false;
        }
    }
 
    /**
     * Forks process
     *
     * @return bool
     */
    private function _fork() {
 
        $this->_logMessage('Forking...');
 
        $pid = pcntl_fork();
 
        if ($pid == -1) {
            // 出错
            $this->_logMessage('Could not fork', self::DLOG_ERROR);
 
            return false;
        } elseif ($pid) {
            // 父进程
            $this->_logMessage('Killing parent');
 
            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('Could not set identity', 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('Shutdown signal');
                exit();
                break;
 
            case SIGCHLD:   // Halt
                $this->_logMessage('Halt signal');
                while (pcntl_waitpid(-1, $status, WNOHANG) > 0);
                break;
            case SIGUSR1:   // User-defined
                $this->_logMessage('User-defined signal 1');
                $this->_sigHandlerUser1();
                break;
            case SIGUSR2:   // User-defined
                $this->_logMessage('User-defined signal 2');
                $this->_sigHandlerUser2();
                break;
        }
    }
 
    /**
     * Signals handler: USR1
     *  主要用于定时清理每个进程里被缓存的域名dns解析记录
     *
     * @return void
     */
    protected function _sigHandlerUser1() {
        apc_clear_cache('user');
    }
 
    /**
     * 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('Releasing daemon');
 
            unlink($this->pidFileLocation);
        }
    }
 
    /**
     * writeProcess
     * 将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程
     *
     * @return void
     */
    public function writeProcess() {
 
        // 初始化 proc
        $this->_initProcessLocation();
 
        $command = trim(implode(' ', $_SERVER['argv']));
 
        // 指定进程的目录
        $processDir = $this->processLocation . '/' . $this->_pid;
        $processCmdFile = $processDir . '/cmd';
        $processPwdFile = $processDir . '/pwd';
 
        // 所有进程所在的目录
        if (!is_dir($this->processLocation)) {
            mkdir($this->processLocation, 0777);
            chmod($processDir, 0777);
        }
 
        // 查询重复的进程记录
        $pDirObject = dir($this->processLocation);
        while ($pDirObject && (($pid = $pDirObject->read()) !== false)) {
            if ($pid == '.' || $pid == '..' || intval($pid) != $pid) {
                continue;
            }
 
            $pDir = $this->processLocation . '/' . $pid;
            $pCmdFile = $pDir . '/cmd';
            $pPwdFile = $pDir . '/pwd';
            $pHeartFile = $pDir . '/heart';
 
            // 根据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['PWD']);
 
        // 写文件有缓存
        usleep(1000);
 
        return true;
    }
 
    /**
     * _initProcessLocation
     * 初始化
     *
     * @return void
     */
    protected function _initProcessLocation() {
 
        $this->processLocation = ROOT_PATH . '/app/data/proc';
        $this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';
    }
}