分享PHP守护进程类
用PHP实现的Daemon类。可以在服务器上实现队列或者脱离crontab的计划任务。
使用的时候,继承于这个类,并重写_doTask方法,通过main初始化执行。
<?php classDaemon{ constDLOG_TO_CONSOLE=1; constDLOG_NOTICE=2; constDLOG_WARNING=4; constDLOG_ERROR=8; constDLOG_CRITICAL=16; constDAPC_PATH='/tmp/daemon_apc_keys'; /** *UserID * *@varint */ public$userID=65534;//nobody /** *GroupID * *@varinteger */ public$groupID=65533;//nobody /** *Terminatedaemonwhensetidentityfailure? * *@varbool *@since1.0.3 */ public$requireSetIdentity=false; /** *PathtoPIDfile * *@varstring *@since1.0.1 */ public$pidFileLocation='/tmp/daemon.pid'; /** *processLocation *进程信息记录目录 * *@varstring */ public$processLocation=''; /** *processHeartLocation *进程心跳包文件 * *@varstring */ public$processHeartLocation=''; /** *Homepath * *@varstring *@since1.0 */ public$homePath='/'; /** *CurrentprocessID * *@varint *@since1.0 */ protected$_pid=0; /** *Isthisprocessachildren * *@varboolean *@since1.0 */ protected$_isChildren=false; /** *Isdaemonrunning * *@varboolean *@since1.0 */ protected$_isRunning=false; /** *Constructor * *@returnvoid */ publicfunction__construct(){ error_reporting(0); set_time_limit(0); ob_implicit_flush(); register_shutdown_function(array(&$this,'releaseDaemon')); } /** *启动进程 * *@returnbool */ publicfunctionmain(){ $this->_logMessage('Startingdaemon'); if(!$this->_daemonize()){ $this->_logMessage('Couldnotstartdaemon',self::DLOG_ERROR); returnfalse; } $this->_logMessage('Running...'); $this->_isRunning=true; while($this->_isRunning){ $this->_doTask(); } returntrue; } /** *停止进程 * *@returnvoid */ publicfunctionstop(){ $this->_logMessage('Stopingdaemon'); $this->_isRunning=false; } /** *Dotask * *@returnvoid */ protectedfunction_doTask(){ //overridethismethod } /** *_logMessage *记录日志 * *@paramstring消息 *@paraminteger级别 *@returnvoid */ protectedfunction_logMessage($msg,$level=self::DLOG_NOTICE){ //overridethismethod } /** *Daemonize * *Severalrulesorcharacteristicsthatmostdaemonspossess: *1)Checkisdaemonalreadyrunning *2)Forkchildprocess *3)Setsidentity *4)Makecurrentprocessasessionlaeder *5)WriteprocessIDtofile *6)Changehomepath *7)umask(0) * *@accessprivate *@since1.0 *@returnvoid */ privatefunction_daemonize(){ ob_end_flush(); if($this->_isDaemonRunning()){ //Deamonisalreadyrunning.Exiting returnfalse; } if(!$this->_fork()){ //Coudn'tfork.Exiting. returnfalse; } if(!$this->_setIdentity()&&$this->requireSetIdentity){ //Requiredidentitysetfailed.Exiting returnfalse; } if(!posix_setsid()){ $this->_logMessage('Couldnotmakethecurrentprocessasessionleader',self::DLOG_ERROR); returnfalse; } if(!$fp=fopen($this->pidFileLocation,'w')){ $this->_logMessage('CouldnotwritetoPIDfile',self::DLOG_ERROR); returnfalse; }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')); returntrue; } /** *Cheksisdaemonalreadyrunning * *@returnbool */ privatefunction_isDaemonRunning(){ $oldPid=file_get_contents($this->pidFileLocation); if($oldPid!==false&&posix_kill(trim($oldPid),0)) { $this->_logMessage('DaemonalreadyrunningwithPID:'.$oldPid,(self::DLOG_TO_CONSOLE|self::DLOG_ERROR)); returntrue; } else { returnfalse; } } /** *Forksprocess * *@returnbool */ privatefunction_fork(){ $this->_logMessage('Forking...'); $pid=pcntl_fork(); if($pid==-1){ //出错 $this->_logMessage('Couldnotfork',self::DLOG_ERROR); returnfalse; }elseif($pid){ //父进程 $this->_logMessage('Killingparent'); exit(); }else{ //fork的子进程 $this->_isChildren=true; $this->_pid=posix_getpid(); returntrue; } } /** *Setsidentityofadaemonandreturnsresult * *@returnbool */ privatefunction_setIdentity(){ if(!posix_setgid($this->groupID)||!posix_setuid($this->userID)) { $this->_logMessage('Couldnotsetidentity',self::DLOG_WARNING); returnfalse; } else { returntrue; } } /** *Signalshandler * *@accesspublic *@since1.0 *@returnvoid */ publicfunctionsigHandler($sigNo){ switch($sigNo) { caseSIGTERM://Shutdown $this->_logMessage('Shutdownsignal'); exit(); break; caseSIGCHLD://Halt $this->_logMessage('Haltsignal'); while(pcntl_waitpid(-1,$status,WNOHANG)>0); break; caseSIGUSR1://User-defined $this->_logMessage('User-definedsignal1'); $this->_sigHandlerUser1(); break; caseSIGUSR2://User-defined $this->_logMessage('User-definedsignal2'); $this->_sigHandlerUser2(); break; } } /** *Signalshandler:USR1 *主要用于定时清理每个进程里被缓存的域名dns解析记录 * *@returnvoid */ protectedfunction_sigHandlerUser1(){ apc_clear_cache('user'); } /** *Signalshandler:USR2 *用于写入心跳包文件 * *@returnvoid */ protectedfunction_sigHandlerUser2(){ $this->_initProcessLocation(); file_put_contents($this->processHeartLocation,time()); returntrue; } /** *Releasesdaemonpidfile *Thismethodiscalledonexit(destructorlike) * *@returnvoid */ publicfunctionreleaseDaemon(){ if($this->_isChildren&&is_file($this->pidFileLocation)){ $this->_logMessage('Releasingdaemon'); unlink($this->pidFileLocation); } } /** *writeProcess *将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程 * *@returnvoid */ publicfunctionwriteProcess(){ //初始化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); returntrue; } /** *_initProcessLocation *初始化 * *@returnvoid */ protectedfunction_initProcessLocation(){ $this->processLocation=ROOT_PATH.'/app/data/proc'; $this->processHeartLocation=$this->processLocation.'/'.$this->_pid.'/heart'; } }