分享PHP守护进程类

用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';
  }
}
(0)

相关推荐

  • PHP高级编程实例:编写守护进程

    1.什么是守护进程 守护进程是脱离于终端并且在后台运行的进程.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断. 例如 apache, nginx, mysql 都是守护进程 2.为什么开发守护进程 很多程序以服务形式存在,他没有终端或UI交互,它可能采用其他方式与其他程序交互,如TCP/UDP Socket, UNIX Socket, fifo.程序一旦启动便进入后台,直到满足条件他便开始处理任务. 3.何时采用守护进程开发应用程

  • 如何写php守护进程(Daemon)

    守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程是一种很有用的进程.php也可以实现守护进程的功能. 一.基本概念 进程: 每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态. 进程组:每个进程都属于一个进程组,每个进程组都有一个进程组号,该号等于该进程组组长的PID 二.守护编程要点 1. 在后台运行               为避免挂起控制终端将Daemon放入后台执行.方法是在进程中调用fork使

  • PHP程序员玩转Linux系列 使用supervisor实现守护进程

    PHP程序员玩转Linux系列文章: 1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转Linux系列-备份还原MySQL 5.PHP程序员玩转Linux系列-自动备份与SVN 6.PHP程序员玩转Linux系列-Linux和Windows安装nginx 7.PHP程序员玩转Linux系列-nginx初学者引导 8.PHP程序员玩转Linux系列-N

  • PHP扩展程序实现守护进程

    一般Server程序都是运行在系统后台,这与普通的交互式命令行程序有很大的区别.glibc里有一个函数daemon.调用此函数,就可使当前进程脱离终端变成一个守护进程,具体内容参见man daemon.PHP中暂时没有此函数,当然如果你有兴趣的话,可以写一个PHP的扩展函数来实现. PHP命令行程序实现守护进程化有2种方法: 一 .使用nohup 复制代码 代码如下: nohup php myprog.php > log.txt & 这里就实现了守护进程化. 单独执行 php myprog.

  • PHP将进程作为守护进程的方法

    本文实例讲述了PHP将进程作为守护进程的方法.分享给大家供大家参考.具体分析如下: php中posix_setsid()的用法 文档解释是"Make the current process a session leader" 参考文档:http://linux.die.net/man/2/setsid 意思就是在一个进程组之间(父进程和子进程)调用这个函数的进程会被选举为进程组的leader 所以让一个进程成为守护进程的方法就是: 1 fork出一个子进程 2 在子进程posix_se

  • PHP程序级守护进程的实现与优化的使用概述

    首先需要解释的是什么是守护进程. 守护进程就是在后台一直运行的进程.比如我们启动的httpd,mysqld等进程都是常驻内存内运行的程序. 针对需求进行分析: 需求:有一个常驻队列messageQueue(假设在redis内存中),这个队列会有可能有请求不定期的往队列中增加元素.同时我们要求在队列中有元素的时候,按照队列顺序将元素pop出来,并进行处理(假设这个处理只是echo 'test'); 解决方法: 现在假设已经有了两个函数 function oPopMessageQueue(){ -}

  • php脚本守护进程原理与实现方法详解

    本文实例讲述了php脚本守护进程原理与实现方法.分享给大家供大家参考,具体如下: 思路: 1. while 循环,若当前没有数据要操作可以休眠: 2. crontab 脚本每隔固定时间段执行该脚本,执行时先检测是否已在执行,若无 执行,有则 跳过. 3. nohup  后台执行 4. flock -xn  加锁 实例: 要执行代码:index.php <?php set_time_limit(0); //死循环 while(1) { $message = '1111111' . "\n&q

  • php守护进程 加linux命令nohup实现任务每秒执行一次

    Unix中 nohup 命令功能就是不挂断地运行命令,同时 nohup 把程序的所有输出到放到当前目录 nohup.out 文件中,如果文件不可写,则放到 <用户主目录>/nohup.out 文件中.那么有了这个命令以后我们php就写成shell 脚本使用循环来让我们脚本一直运行下去,不管我们终端窗口是否关闭都能够让我们php 脚本一直运行下去. 马上动手写个 PHP 小程序,功能为每30秒记录时间,写入到文件 复制代码 代码如下: # vi for_ever.php #! /usr/loca

  • PHP守护进程的两种常见实现方式详解

    本文实例讲述了PHP守护进程的两种常见实现方式.分享给大家供大家参考,具体如下: 第一种方式,借助 nohup 和 &  配合使用. 在命令后面加上 & 符号, 可以让启动的进程转到后台运行,而不占用控制台,控制台还可以再运行其他命令,这里我使用一个while死循环来做演示,代码如下 <?php while(true){ echo time().PHP_EOL; sleep(3); } 用 & 方式来启动该进程 [root@localhost php]# php deadlo

  • PHP守护进程实例

    php也是可以直接进行守护进程的启动与终止的,相对于shell来说会简单很多,理解更方便,当然了php的守护进程要实现自动重启还是要依赖于shell的crontab日程表,每隔一段时间去执行一次脚本看脚本是否需要重启,如果需要则杀掉进程删除RunFile文件,重新启动并在RunFile文件中写入pid. 复制代码 代码如下: <?php       function start($file){     $path = dirname(__FILE__).'/';     $runfile = $

  • shell脚本作为保证PHP脚本不挂掉的守护进程实例分享

    前几天开始跑一份数据名单,名单需要提供用户名.是否有手机号.是否有邮箱,用户名单我轻易的获取到了,但是,用户名单有2000w之多,并且去检测用户是否有手机号.是否有邮箱必须得通过一个对外开放的安全接口一个一个用户去请求,然后分析返回值才能知道.下面是我处理的方案:1.将2000w名单保存到临时数据表2.用PHP程序每次从该表获取500个用户,检测完后生成SQL update原纪录3.为了防止PHP程序突然断掉,用shell脚本每隔1分钟检测,PHP挂掉了则重启我使用shell脚本作为守护进程的原

  • PHP实现多进程并行操作的详解(可做守护进程)

    如下所示: 复制代码 代码如下: /** * 入口函数 * 将此文件保存为 ProcessOpera.php * 在terminal中运行 /usr/local/php/bin/php ProcessOpera.php & * 查看进程 ps aux|grep php */ProcessOpera("runCode", array(), 8); /** * run Code */function runCode($opt = array()) {   //需要在守护进程中运行的

随机推荐