PHP 守护进程类

2023-05-16

出处: http://www.oschina.net/code/snippet_239150_11088


用 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 =  '/tmp/daemon_apc_keys';

    /**
     * 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 = '/tmp/daemon.pid';

    /**
     * processLocation
     * 进程信息记录目录
     *
     * @var string
     */
    public $processLocation = '';

    /**
     * processHeartLocation
     * 进程心跳包文件
     *
     * @var string
     */
    public $processHeartLocation = '';

    /**
     * Home path
     *
     * @var string
     * @since 1.0
     */
    public $homePath = '/';

    /**
     * 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, 'releaseDaemon'));
    }

    /**
     * 启动进程
     *
     * @return bool
     */
    public function main() {

        $this->_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';
    }
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PHP 守护进程类 的相关文章

随机推荐

  • JDK5.0新特性(2)——反射

    文章标题 JDK5 0 新特性 xff08 2 xff09 反射 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi
  • JDK5.0新特性(3)——枚举

    文章标题 JDK5 0 新特性 xff08 3 xff09 枚举 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi
  • XML学习笔记(附上思维导图)

    文章标题 XML 学习笔记 附上思维导图 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi http newjue
  • JavaWeb基础(思维导图版)

    尝试一下只画思维导图
  • 周深与邓丽君同台,vtuber能扛起元宇宙的大旗吗?

    在今年江苏卫视的跨年晚会上 xff0c 周深与和与邓丽君合作了 大鱼海棠 小城故事 等三首曲目 xff0c 虽然仔细看邓丽君的形象还是略有破绽 xff0c 但整体的自然度已经过关了 本次重现邓丽君的形象 xff0c 使用的类似于vtuber
  • Java线程类小结(附思维导图)

    文章标题 Java 线程类小结 xff08 附思维导图 xff09 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueq
  • 打造山寨产品和伪造产品的利器——装饰模式

    文章标题 打造山寨产品和伪造产品的利器 装饰模式 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi http ne
  • 搜索文本内容——Java代码的简单实现

    文章标题 搜索文本内容 Java 代码的简单实现 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi http ne
  • 用面向对象的思想探讨游戏“魔兽争霸”(1)

    文章标题 用面向对象的思想探讨游戏 魔兽争霸 1 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net newjueqi http ne
  • 用面向对象的思想探讨游戏“魔兽争霸”(2)-继承和多态的应用(修改版)

    文章标题 用面向对象的思想探讨游戏 魔兽争霸 2 继承和多态的应用 修改版 文章作者 曾健生 作者邮箱 zengjiansheng1 64 126 com 作者QQ 190678908 作者博客 http blog csdn net new
  • memcache与mysql数据库同步

    http www cnblogs com zhanghw0354 archive 2012 10 23 2735599 html Good Heavens memcache与mysql数据库同步 面试某电商时 xff0c 面试官问到了mem
  • 默认的room setting

  • openfire的安装

    安装很简单 xff0c 使用命令 rpm ivh openfire 1 i386 rpm 在启动过程中可能出现如下的错误 xff1a failed to run command 96 opt openfire jre bin java 39
  • PHP 做守护进程

    http www 21andy com blog 20100228 1728 html Unix中 nohup 命令功能就是不挂断地运行命令 xff0c 同时 nohup 把程序的所有输出到放到当前目录 nohup out 文件中 xff0
  • php中获取http请求的代码

    获取http的请求 public function get http raw raw 61 39 39 1 请求行 raw 61 SERVER 39 REQUEST METHOD 39 39 39 SERVER 39 REQUEST URI
  • 应用向左,理论向右,计算机2021的冰火两重天

    近来来计算理论的发展极其缓慢 xff0c 而与之对应的是计算机领域的应用侧发展可谓日新月异 xff0c 像GPT 3及其衍生的AI模型 xff0c 各类大数据模型 超大规模云平台等等方面的进展不胜枚举 xff0c 相关成果也都举世瞩目 xf
  • mysqldump常用命令

    http www blogjava net Alpha archive 2007 08 10 135694 html 1 导出整个数据库 mysqldump u 用户名 p 数据库名 gt 导出的文件名 mysqldump u wcnc p
  • ci中使用多个数据库的方法

    config 39 hostname 39 61 config item 39 database ip 39 config 39 username 39 61 config item 39 database username 39 conf
  • php中强制浏览器下载文件

    file 61 fopen written file 39 r 39 header 39 Content type application octet stream 39 header 39 Accept Ranges bytes 39 h
  • PHP 守护进程类

    出处 xff1a http www oschina net code snippet 239150 11088 用 PHP 实现的 Daemon 类 可以在服务器上实现队列或者脱离 crontab 的计划任务 使用的时候 xff0c 继承于