分享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';
}
}