用 php 实现的 daemon 类。可以在服务器上实现队列或者脱离 crontab 的计划任务。
使用的时候,继承于这个类,并重写 _dotask 方法,通过 main 初始化执行。
_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';
}
}