搜尋
首頁php教程PHP源码PHP 守護程式類

PHP 守護程式類

Nov 08, 2016 pm 05:37 PM

用 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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器