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