【转】KalonDaemon - 守护进程PHP版
(2011-10-18 17:43:24)
标签:
守护进程php5工作流程终止杂谈 |
分类: 开发相关 |
原文地址:http://blog.csdn.net/phpkernel/article/details/6458991
守护进程也称精灵进程(daemon),是生存期较长的一种进程。它们常常用在系统自举时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。UNIX类操作系统有很多的守护进程,它们执行日常事务活动。
目前有大量的web站点基与PHP开发,业务逻辑都是由PHP来实现,很多时候我们
也需要一个PHP的daemon来做一些日常事务,例如我们想每隔一个小时统计一下数据库中的某项数据,每天定期的执行一些备份或则监控任务。这些任务在
apache模块的web环境下实现比较困难而且容易引发很多问题。
这里我介绍一款我自己写的PHP5版的daemon类 -
KalonDaemon.
概要:
使用方式:
工作流程:
KalonDaemon遵循大部分unix类系统下的守护进程编程规则,主要工作流程如下:
1.
调用pcntl_fork,然后使父进程退出(exit).这样做实现如下几点:第一,如果该守护进程是作为一条shell命令启动,那么父进程终止使得
shell认为这条命令已经执行完毕;第二,子进程继承父进程的进程组ID,但是具有一个新的进程ID,这就保证了子进程不是一个进程组的组长,这对于下
面要做的posix_setsid调用是必要的前提条件。
2.调用posix_setsid以创建一个新的会话,这样新进程就成为了新会话的首进程,同时是新进程组的组长进程,而且没有控制终端。
3.设置进程信号回调函数,方便我们用其它进程对守护进程进行控制。
以下是mydaemon.php的源码:
在命令行下执行:
/path/to/phpcli/php
输出如下信息:
Daemon started with pid 8976...
running.
说明守护进程已经开始运行,进程号为8976,当然一般情况进程号每次都会不一样。
由于mydaemon.php中有一个死循环,每次循环会睡眠1000秒,所以进程永远不会终止。
mydaemon.php中为守护进程注册了两个信号句柄,信号SIGUSR1对应函数myHandler1(), 信号SIGUSR2对应myHandler2(),我们可以通过kill命令给进程发送这两个信号来唤醒进程。
kill -SIGUSR2 8976
输出信息如下:
This handler2 works.
running.
说明睡眠中的进程被唤醒,并且执行了myHandler2()函数,然后再次进入了循环。
当我们需要终止守护进程的时候,可以用以下命令:
/path/to/phpcli/php
输出信息如下:
Daemon stopped with pid 8976...
这样守护进程就终止了。
这样的特性可以在某些应用场景非常有用,比如服务器在接受到一些上传的数据之后,需要唤醒守护进程来处理这些数据。守护进程可以长期出去睡眠状态等 待,当数据到来之后,发送信号唤醒守护进程,守护进程马上开始处理这些数据。这样要比定期的轮询效率高很多,而且不会有延迟现象。
KalonDaemon.php
<?php
class KalonDaemon
{
private $_pidFilePath = "/var/run";
private $_pidFileName = "daemon.pid";
private $_verbose = false;
private $_singleton = true;
private $_closeStdHandle = true;
private $_pid = 0;
private $_execFile = "";
public function __construct($configs = array())
{
}
public function setConfigs($configs)
{
}
public function setPidFilePath($path)
{
}
public function getPidFilePath()
{
}
public function setPidFileName($name)
{
}
public function getPidFileName()
{
}
public function setVerbose($open = true)
{
}
public function getVerbose()
{
}
public function setSingleton($singleton = true)
{
}
public function getSingleton()
{
}
public function setCloseStdHandle($close = true)
{
}
public function getCloseStdHandle()
{
}
public function start()
{
$this->_daemonize();
}
public function stop($force = false)
{
if (false === ($pid = $this->_getPidFromFile()))
if (!posix_kill($pid, $signo)) {
}
$this->_unlinkPidFile();
$this->_out("Daemon stopped with pid {$pid}...");
return true;
}
public function restart()
{
$this->stop();
//sleep to wait
sleep(1);
$this->start();
}
public function getDaemonPid()
{
return $this->_getPidFromFile();
}
public function signalHandler($signo)
{
$signFuns = $this->_signalHandlerFuns[$signo];
if (is_array($signFuns)) {
}
//default action
switch ($signo) {
case SIGTERM:
exit;
break;
default:
// handle all other signals
}
}
public function addSignalHandler($signo, $fun)
{
}
public function sendSignal($signo)
{
if (false === ($pid = $this->_getPidFromFile()))
if (!posix_kill($pid, $signo)) {
}
//$this->_out("Send signal $signo to pid $pid...");
return true;
}
public function isActive()
{
try {
$pid = $this->_getPidFromFile();
} catch (KalonDaemonException $e) {
return false;
}
if (false === $pid)
if (false === ($active = @pcntl_getpriority($pid)))
}
private function _daemonize()
{
//single model, first check if running
if ($this->_singleton) {
}
//fork current process
$pid = pcntl_fork();
if ($pid == -1) {
//fork error
throw new KalonDaemonException("Error happened while fork process");
} elseif ($pid) {
//parent exit
exit();
} else {
//child, get pid
$this->_pid = posix_getpid();
}
$this->_out("Daemon started with pid {$this->_pid}...");
//detach from controlling terminal
if (!posix_setsid())
throw new KalonDaemonException("Cannot detach from
terminal");
//log pid in singleton model
if ($this->_singleton)
return $this->_pid;
}
private function _getPidFromFile()
{
$pidFile = $this->_pidFilePath . "/" . $this->_pidFileName;
//no pid file,it's the first time of running
if (!file_exists($pidFile))
if (!$handle = fopen($pidFile, "r"))
throw new KalonDaemonException("Cannot open pid file {$pidFile}
for read");
if (($pid = fread($handle, 1024)) ===
false)
throw new KalonDaemonException("Cannot read from pid file
{$pidFile}");
fclose($handle);
return $this->_pid = (int) $pid;
}
private function _checkRunning()
{
$pid = $this->_getPidFromFile();
//no pid file,not running
if(false === $pid)
//get exe file path from pid
{
case "freebsd":
$strExe = $this->_getFreebsdProcExe($pid);
if($strExe === false)
return false;
$strArgs = $this->_getFreebsdProcArgs($pid);
break;
case "linux":
$strExe = $this->_getLinuxProcExe($pid);
if($strExe === false)
return false;
$strArgs = $this->_getLinuxProcArgs($pid);
break;
default:
return false;
}
$exeRealPath = $this->_getDaemonRealPath($strArgs, $pid);
//get exe file path from command
if ($strExe != PHP_BINDIR . "/php")
switch($sapi)
{
case "cgi":
case "cgi-fcgi":
$selfFile = $_SERVER['argv'][0];
break;
default:
$selfFile = $_SERVER['PHP_SELF'];
break;
}
$currentRealPath = realpath($selfFile);
//compare two path
if ($currentRealPath != $exeRealPath)
}
private function _logPid()
{
$pidFile = $this->_pidFilePath . "/" . $this->_pidFileName;
if (!$handle = fopen($pidFile, "w")) {
throw new KalonDaemonException("Cannot open pid file {$pidFile}
for write");
}
if (fwrite($handle, $this->_pid) == false) {
throw new KalonDaemonException("Cannot write to pid file
{$pidFile}");
}
fclose($handle);
}
private function _getDaemonRealPath($daemonFile, $daemonPid)
{
$daemonFile = trim($daemonFile);
if(substr($daemonFile,0,1) !== "/") {
$cwd = $this->_getLinuxProcCwd($daemonPid);
$cwd = rtrim($cwd, "/");
$cwd = $cwd . "/" . $daemonFile;
$cwd = realpath($cwd);
return $cwd;
}
return realpath($daemonFile);
}
private function _getFreebsdProcExe($pid)
{
$strProcExeFile = "/proc/" . $pid . "/file";
if (false === ($strLink = @readlink($strProcExeFile))) {
}
return $strLink;
}
private function _getLinuxProcExe($pid)
{
$strProcExeFile = "/proc/" . $pid . "/exe";
if (false === ($strLink = @readlink($strProcExeFile))) {
}
return $strLink;
}
private function _getFreebsdProcArgs($pid)
{
return $this->_getLinuxProcArgs($pid);
}
private function _getLinuxProcArgs($pid)
{
$strProcCmdlineFile = "/proc/" . $pid . "/cmdline";
if (!$fp = @fopen($strProcCmdlineFile, "r")) {
}
if (!$strContents = fread($fp, 4096)) {
throw new KalonDaemonException("Cannot read or empty file
{$strProcCmdlineFile}");
}
fclose($fp);
$strContents = preg_replace("/[^/w/.///-]/", " "
, trim($strContents));
$strContents = preg_replace("//s+/", " ", $strContents);
$arrTemp = explode(" ", $strContents);
if(count($arrTemp) < 2) {
}
return trim($arrTemp[1]);
}
private function _getLinuxProcCwd($pid)
{
$strProcExeFile = "/proc/" . $pid . "/cwd";
if (false === ($strLink = @readlink($strProcExeFile))) {
}
return $strLink;
}
private function _out($str)
{
}
}
class KalonDaemonException extends
Exception
{
}
?>