Chinaunix首页 | 论坛 | 博客
  • 博客访问: 909991
  • 博文数量: 91
  • 博客积分: 803
  • 博客等级: 准尉
  • 技术积分: 1051
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-24 13:42
文章分类

全部博文(91)

文章存档

2021年(1)

2020年(4)

2019年(4)

2018年(9)

2017年(11)

2016年(11)

2015年(6)

2014年(3)

2013年(28)

2012年(14)

分类: PHP

2016-04-11 10:32:50

用 PHP 实现的 Daemon 类。可以在服务器上实现队列或者脱离 crontab 的计划任务。 
使用的时候,继承于这个类,并重写 _doTask 方法,通过 main 初始化执行。

点击(此处)折叠或打开

  1. <?php
  2.  
  3. class Daemon {
  4.  
  5.     const DLOG_TO_CONSOLE = 1;
  6.     const DLOG_NOTICE = 2;
  7.     const DLOG_WARNING = 4;
  8.     const DLOG_ERROR = 8;
  9.     const DLOG_CRITICAL = 16;
  10.  
  11.     const DAPC_PATH = '/tmp/daemon_apc_keys';
  12.  
  13.     /**
  14.      * User ID
  15.      *
  16.      * @var int
  17.      */
  18.     public $userID = 65534; // nobody
  19.  
  20.     /**
  21.      * Group ID
  22.      *
  23.      * @var integer
  24.      */
  25.     public $groupID = 65533; // nobody
  26.  
  27.     /**
  28.      * Terminate daemon when set identity failure ?
  29.      *
  30.      * @var bool
  31.      * @since 1.0.3
  32.      */
  33.     public $requireSetIdentity = false;
  34.  
  35.     /**
  36.      * Path to PID file
  37.      *
  38.      * @var string
  39.      * @since 1.0.1
  40.      */
  41.     public $pidFileLocation = '/tmp/daemon.pid';
  42.  
  43.     /**
  44.      * processLocation
  45.      * 进程信息记录目录
  46.      *
  47.      * @var string
  48.      */
  49.     public $processLocation = '';
  50.  
  51.     /**
  52.      * processHeartLocation
  53.      * 进程心跳包文件
  54.      *
  55.      * @var string
  56.      */
  57.     public $processHeartLocation = '';
  58.  
  59.     /**
  60.      * Home path
  61.      *
  62.      * @var string
  63.      * @since 1.0
  64.      */
  65.     public $homePath = '/';
  66.  
  67.     /**
  68.      * Current process ID
  69.      *
  70.      * @var int
  71.      * @since 1.0
  72.      */
  73.     protected $_pid = 0;
  74.  
  75.     /**
  76.      * Is this process a children
  77.      *
  78.      * @var boolean
  79.      * @since 1.0
  80.      */
  81.     protected $_isChildren = false;
  82.  
  83.     /**
  84.      * Is daemon running
  85.      *
  86.      * @var boolean
  87.      * @since 1.0
  88.      */
  89.     protected $_isRunning = false;
  90.  
  91.     /**
  92.      * Constructor
  93.      *
  94.      * @return void
  95.      */
  96.     public function __construct() {
  97.  
  98.         error_reporting(0);
  99.         set_time_limit(0);
  100.         ob_implicit_flush();
  101.  
  102.         register_shutdown_function(array(&$this, 'releaseDaemon'));
  103.     }
  104.  
  105.     /**
  106.      * 启动进程
  107.      *
  108.      * @return bool
  109.      */
  110.     public function main() {
  111.  
  112.         $this->_logMessage('Starting daemon');
  113.  
  114.         if (!$this->_daemonize()) {
  115.             $this->_logMessage('Could not start daemon', self::DLOG_ERROR);
  116.  
  117.             return false;
  118.         }
  119.  
  120.         $this->_logMessage('Running...');
  121.  
  122.         $this->_isRunning = true;
  123.  
  124.         while ($this->_isRunning) {
  125.             $this->_doTask();
  126.         }
  127.  
  128.         return true;
  129.     }
  130.  
  131.     /**
  132.      * 停止进程
  133.      *
  134.      * @return void
  135.      */
  136.     public function stop() {
  137.  
  138.         $this->_logMessage('Stoping daemon');
  139.  
  140.         $this->_isRunning = false;
  141.     }
  142.  
  143.     /**
  144.      * Do task
  145.      *
  146.      * @return void
  147.      */
  148.     protected function _doTask() {
  149.         // override this method
  150.     }
  151.  
  152.     /**
  153.      * _logMessage
  154.      * 记录日志
  155.      *
  156.      * @param string 消息
  157.      * @param integer 级别
  158.      * @return void
  159.      */
  160.     protected function _logMessage($msg, $level = self::DLOG_NOTICE) {
  161.         // override this method
  162.     }
  163.  
  164.     /**
  165.      * Daemonize
  166.      *
  167.      * Several rules or characteristics that most daemons possess:
  168.      * 1) Check is daemon already running
  169.      * 2) Fork child process
  170.      * 3) Sets identity
  171.      * 4) Make current process a session laeder
  172.      * 5) Write process ID to file
  173.      * 6) Change home path
  174.      * 7) umask(0)
  175.      *
  176.      * @access private
  177.      * @since 1.0
  178.      * @return void
  179.      */
  180.     private function _daemonize() {
  181.  
  182.         ob_end_flush();
  183.  
  184.         if ($this->_isDaemonRunning()) {
  185.             // Deamon is already running. Exiting
  186.             return false;
  187.         }
  188.  
  189.         if (!$this->_fork()) {
  190.             // Coudn't fork. Exiting.
  191.             return false;
  192.         }
  193.  
  194.         if (!$this->_setIdentity() && $this->requireSetIdentity) {
  195.             // Required identity set failed. Exiting
  196.             return false;
  197.         }
  198.  
  199.         if (!posix_setsid()) {
  200.             $this->_logMessage('Could not make the current process a session leader', self::DLOG_ERROR);
  201.  
  202.             return false;
  203.         }
  204.  
  205.         if (!$fp = fopen($this->pidFileLocation, 'w')) {
  206.             $this->_logMessage('Could not write to PID file', self::DLOG_ERROR);
  207.             return false;
  208.         } else {
  209.             fputs($fp, $this->_pid);
  210.             fclose($fp);
  211.         }
  212.  
  213.         // 写入监控日志
  214.         $this->writeProcess();
  215.  
  216.         chdir($this->homePath);
  217.         umask(0);
  218.  
  219.         declare(ticks = 1);
  220.  
  221.         pcntl_signal(SIGCHLD, array(&$this, 'sigHandler'));
  222.         pcntl_signal(SIGTERM, array(&$this, 'sigHandler'));
  223.         pcntl_signal(SIGUSR1, array(&$this, 'sigHandler'));
  224.         pcntl_signal(SIGUSR2, array(&$this, 'sigHandler'));
  225.  
  226.         return true;
  227.     }
  228.  
  229.     /**
  230.      * Cheks is daemon already running
  231.      *
  232.      * @return bool
  233.      */
  234.     private function _isDaemonRunning() {
  235.  
  236.         $oldPid = file_get_contents($this->pidFileLocation);
  237.  
  238.         if ($oldPid !== false && posix_kill(trim($oldPid),0))
  239.         {
  240.             $this->_logMessage('Daemon already running with PID: '.$oldPid, (self::DLOG_TO_CONSOLE | self::DLOG_ERROR));
  241.  
  242.             return true;
  243.         }
  244.         else
  245.         {
  246.             return false;
  247.         }
  248.     }
  249.  
  250.     /**
  251.      * Forks process
  252.      *
  253.      * @return bool
  254.      */
  255.     private function _fork() {
  256.  
  257.         $this->_logMessage('Forking...');
  258.  
  259.         $pid = pcntl_fork();
  260.  
  261.         if ($pid == -1) {
  262.             // 出错
  263.             $this->_logMessage('Could not fork', self::DLOG_ERROR);
  264.  
  265.             return false;
  266.         } elseif ($pid) {
  267.             // 父进程
  268.             $this->_logMessage('Killing parent');
  269.  
  270.             exit();
  271.         } else {
  272.             // fork的子进程
  273.             $this->_isChildren = true;
  274.             $this->_pid = posix_getpid();
  275.  
  276.             return true;
  277.         }
  278.     }
  279.  
  280.     /**
  281.      * Sets identity of a daemon and returns result
  282.      *
  283.      * @return bool
  284.      */
  285.     private function _setIdentity() {
  286.  
  287.         if (!posix_setgid($this->groupID) || !posix_setuid($this->userID))
  288.         {
  289.             $this->_logMessage('Could not set identity', self::DLOG_WARNING);
  290.  
  291.             return false;
  292.         }
  293.         else
  294.         {
  295.             return true;
  296.         }
  297.     }
  298.  
  299.     /**
  300.      * Signals handler
  301.      *
  302.      * @access public
  303.      * @since 1.0
  304.      * @return void
  305.      */
  306.     public function sigHandler($sigNo) {
  307.  
  308.         switch ($sigNo)
  309.         {
  310.             case SIGTERM: // Shutdown
  311.                 $this->_logMessage('Shutdown signal');
  312.                 exit();
  313.                 break;
  314.  
  315.             case SIGCHLD: // Halt
  316.                 $this->_logMessage('Halt signal');
  317.                 while (pcntl_waitpid(-1, $status, WNOHANG) > 0);
  318.                 break;
  319.             case SIGUSR1: // User-defined
  320.                 $this->_logMessage('User-defined signal 1');
  321.                 $this->_sigHandlerUser1();
  322.                 break;
  323.             case SIGUSR2: // User-defined
  324.                 $this->_logMessage('User-defined signal 2');
  325.                 $this->_sigHandlerUser2();
  326.                 break;
  327.         }
  328.     }
  329.  
  330.     /**
  331.      * Signals handler: USR1
  332.      * 主要用于定时清理每个进程里被缓存的域名dns解析记录
  333.      *
  334.      * @return void
  335.      */
  336.     protected function _sigHandlerUser1() {
  337.         apc_clear_cache('user');
  338.     }
  339.  
  340.     /**
  341.      * Signals handler: USR2
  342.      * 用于写入心跳包文件
  343.      *
  344.      * @return void
  345.      */
  346.     protected function _sigHandlerUser2() {
  347.  
  348.         $this->_initProcessLocation();
  349.  
  350.         file_put_contents($this->processHeartLocation, time());
  351.  
  352.         return true;
  353.     }
  354.  
  355.     /**
  356.      * Releases daemon pid file
  357.      * This method is called on exit (destructor like)
  358.      *
  359.      * @return void
  360.      */
  361.     public function releaseDaemon() {
  362.  
  363.         if ($this->_isChildren && is_file($this->pidFileLocation)) {
  364.             $this->_logMessage('Releasing daemon');
  365.  
  366.             unlink($this->pidFileLocation);
  367.         }
  368.     }
  369.  
  370.     /**
  371.      * writeProcess
  372.      * 将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程
  373.      *
  374.      * @return void
  375.      */
  376.     public function writeProcess() {
  377.  
  378.         // 初始化 proc
  379.         $this->_initProcessLocation();
  380.  
  381.         $command = trim(implode(' ', $_SERVER['argv']));
  382.  
  383.         // 指定进程的目录
  384.         $processDir = $this->processLocation . '/' . $this->_pid;
  385.         $processCmdFile = $processDir . '/cmd';
  386.         $processPwdFile = $processDir . '/pwd';
  387.  
  388.         // 所有进程所在的目录
  389.         if (!is_dir($this->processLocation)) {
  390.             mkdir($this->processLocation, 0777);
  391.             chmod($processDir, 0777);
  392.         }
  393.  
  394.         // 查询重复的进程记录
  395.         $pDirObject = dir($this->processLocation);
  396.         while ($pDirObject && (($pid = $pDirObject->read()) !== false)) {
  397.             if ($pid == '.' || $pid == '..' || intval($pid) != $pid) {
  398.                 continue;
  399.             }
  400.  
  401.             $pDir = $this->processLocation . '/' . $pid;
  402.             $pCmdFile = $pDir . '/cmd';
  403.             $pPwdFile = $pDir . '/pwd';
  404.             $pHeartFile = $pDir . '/heart';
  405.  
  406.             // 根据cmd检查启动相同参数的进程
  407.             if (is_file($pCmdFile) && trim(file_get_contents($pCmdFile)) == $command) {
  408.                 unlink($pCmdFile);
  409.                 unlink($pPwdFile);
  410.                 unlink($pHeartFile);
  411.  
  412.                 // 删目录有缓存
  413.                 usleep(1000);
  414.  
  415.                 rmdir($pDir);
  416.             }
  417.         }
  418.  
  419.         // 新进程目录
  420.         if (!is_dir($processDir)) {
  421.             mkdir($processDir, 0777);
  422.             chmod($processDir, 0777);
  423.         }
  424.  
  425.         // 写入命令参数
  426.         file_put_contents($processCmdFile, $command);
  427.         file_put_contents($processPwdFile, $_SERVER['PWD']);
  428.  
  429.         // 写文件有缓存
  430.         usleep(1000);
  431.  
  432.         return true;
  433.     }
  434.  
  435.     /**
  436.      * _initProcessLocation
  437.      * 初始化
  438.      *
  439.      * @return void
  440.      */
  441.     protected function _initProcessLocation() {
  442.  
  443.         $this->processLocation = '/data/php-daemon';
            $this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';
        }
    }

阅读(3802) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~