用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:
-
- Check is daemon already running
-
- Fork child process
-
- Sets identity
-
- Make current process a session laeder
-
- Write process ID to file
-
- Change home path
-
- 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) {
// <a href="/tag/required/" target="_blank" class="keywords">required</a> identity set <a href="/tag/Failed/" target="_blank" class="keywords">Failed</a>. 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,'sigHandler'));
pcntl_signal(SIGUSR1,'sigHandler'));
pcntl_signal(SIGUSR2,'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);
}
// <a href="/tag/chaxun/" target="_blank" class="keywords">查询</a>重复的进程记录
$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);
}
// 写入命令参数
file_put_contents($processCmdFile,$command);
file_put_contents($processPwdFile,$_SERVER['PWD']);
// 写<a href="/tag/wenjian/" target="_blank" class="keywords">文件</a>有缓存
usleep(1000);
return true;
}
/**
- _initProcessLocation
- 初始化
- @return void
*/
protected function _initProcessLocation() {
$this->processLocation = ROOT_PATH . '/app/data/proc';
$this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';
}
}