分享PHP守护进程类
程序员文章站
2024-01-11 08:56:22
用php实现的daemon类。可以在服务器上实现队列或者脱离 crontab 的计划任务。
使用的时候,继承于这个类,并重写 _dotask 方法,通过 m...
用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'; } }