分享PHP守护进程类


Posted in PHP onDecember 30, 2015

用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';
  }
}
PHP 相关文章推荐
一个简单实现多条件查询的例子
Oct 09 PHP
PHP中用header图片地址 简单隐藏图片源地址
Apr 09 PHP
PHP闭包(Closure)使用详解
May 02 PHP
php实现加减法验证码代码
Feb 14 PHP
php上传图片存入数据库示例分享
Mar 11 PHP
php读取目录及子目录下所有文件名的方法
Oct 20 PHP
php实现监控varnish缓存服务器的状态
Dec 30 PHP
Java和PHP在Web开发方面对比分析
Mar 01 PHP
深入理解PHP变量的值类型和引用类型
Oct 21 PHP
php mysql like 实现多关键词搜索的方法
Oct 29 PHP
Linux下 php7安装redis的方法
Nov 01 PHP
PHP yield关键字功能与用法分析
Jan 03 PHP
如何写php守护进程(Daemon)
Dec 30 #PHP
PHP汉字转换拼音的函数代码
Dec 30 #PHP
使用PHP如何实现高效安全的ftp服务器(二)
Dec 30 #PHP
php获取当前页面完整URL地址
Dec 30 #PHP
详解WordPress中添加和执行动作的函数使用方法
Dec 29 #PHP
详解WordPress中创建和添加过滤器的相关PHP函数
Dec 29 #PHP
yii,CI,yaf框架+smarty模板使用方法
Dec 29 #PHP
You might like
在PHP里得到前天和昨天的日期的代码
2007/08/16 PHP
php生成excel列名超过26列大于Z时的解决方法
2014/12/29 PHP
php使用正则表达式进行字符串搜索的方法
2015/03/23 PHP
php生成验证码,缩略图及水印图的类分享
2016/04/07 PHP
搜索附近的人PHP实现代码
2018/02/11 PHP
PHP实现随机数字、字母的验证码功能
2018/08/01 PHP
PHP+iframe模拟Ajax上传文件功能示例
2019/07/02 PHP
Laravel 实现在Blade模版中使用全局变量代替路径的例子
2019/10/22 PHP
浅析PHP反序列化中过滤函数使用不当导致的对象注入问题
2020/02/15 PHP
基于jQuery的烟花效果(运动相关)点击屏幕出烟花
2012/06/14 Javascript
了不起的node.js读书笔记之node.js中的特性
2014/12/22 Javascript
jQuery中size()方法用法实例
2014/12/27 Javascript
基于JavaScript实现屏幕滚动效果
2017/01/18 Javascript
彻底学会Angular.js中的transclusion
2017/03/12 Javascript
js指定步长实现单方向匀速运动
2017/07/17 Javascript
基于Vue实现支持按周切换的日历
2020/09/24 Javascript
vue 解决addRoutes动态添加路由后刷新失效问题
2018/07/02 Javascript
总结javascript三元运算符知识点
2018/09/28 Javascript
vue实现导航标题栏随页面滚动渐隐渐显效果
2020/03/12 Javascript
javascript设计模式 ? 命令模式原理与用法实例分析
2020/04/20 Javascript
针对Vue路由history模式下Nginx后台配置操作
2020/10/22 Javascript
跟老齐学Python之做一个小游戏
2014/09/28 Python
整理Python最基本的操作字典的方法
2015/04/24 Python
Python中关于使用模块的基础知识
2015/05/24 Python
python-str,list,set间的转换实例
2018/06/27 Python
Python自定义装饰器原理与用法实例分析
2018/07/16 Python
PyQt5创建一个新窗口的实例
2019/06/20 Python
python已协程方式处理任务实现过程
2019/12/27 Python
Nili Lotan官网:Nili Lotan同名品牌
2018/01/07 全球购物
西班牙宠物用品和食品网上商店:Tiendanimal
2019/06/06 全球购物
实习生自我鉴定
2013/12/12 职场文书
安全生产网格化管理实施方案
2014/03/01 职场文书
电焊工岗位职责
2014/03/06 职场文书
2016猴年开门红标语口号
2015/12/26 职场文书
SpringBoot工程下使用OpenFeign的坑及解决
2021/07/02 Java/Android
redis击穿 雪崩 穿透超详细解决方案梳理
2022/03/17 Redis