用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. Exitingreturn false;} if (!$this->_fork()) {// Coudn"t fork. Exiting.return false;} if (!$this->_setIdentity() && $this->requireSetIdentity) {// Required identity set failed. Exitingreturn 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";}}