加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

【转】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是一款PHP5的daemon类,我们在PHP代码中可以直接包含并且使用,KalonDaemon工作在cli sapi下( command line interface),它能把一个普通的PHP进程变成一个守护进程。
使用方式:
  在PHP脚本中包含了KalonDaemon设置好参数然后调用start()方法。然后我们在命令行下用PHP cli执行脚本,比如cli sapi路径为 /usr/local/bin/php,   我们编写的程序路径 /home/test/mydaemon.php,那么我们用以下方式运行程序:          /usr/local/bin/php  /home/test/mydaemon.php   根据需要可以在后面添加别的参数。
工作流程:
KalonDaemon遵循大部分unix类系统下的守护进程编程规则,主要工作流程如下:
1. 调用pcntl_fork,然后使父进程退出(exit).这样做实现如下几点:第一,如果该守护进程是作为一条shell命令启动,那么父进程终止使得 shell认为这条命令已经执行完毕;第二,子进程继承父进程的进程组ID,但是具有一个新的进程ID,这就保证了子进程不是一个进程组的组长,这对于下 面要做的posix_setsid调用是必要的前提条件。
2.调用posix_setsid以创建一个新的会话,这样新进程就成为了新会话的首进程,同时是新进程组的组长进程,而且没有控制终端。
3.设置进程信号回调函数,方便我们用其它进程对守护进程进行控制。

 

以下是mydaemon.php的源码:

 

<?php
require_once './KalonDaemon.php';
declare(ticks = 1);
$toDo = $_SERVER['argv'][1];
$daemonConf = array('pidFileName' => 'mydaemon.pid',
                    'verbose'     => true);
function myHandler1()
{
    sleep(5);
    echo "This handler1 works./n";
}
function myHandler2()
{
   echo "This handler2 works./n";
}
try {
    $daemon = new KalonDaemon($daemonConf);
    if ($toDo == 'start') {
        $daemon->addSignalHandler(SIGUSR1, 'myHandler1');
        $daemon->addSignalHandler(SIGUSR2, 'myHandler2');
        $daemon->start();
        for (;;) {
            echo "running./n";
            sleep(1000);
        }
    } elseif ($toDo == 'stop') {
        $daemon->stop();
    } else {
        die("unknown action.");
    }
} catch (KalonDaemonException $e) {
    echo $e->getMessage();
    echo "/n";
}
?>


在命令行下执行:

/path/to/phpcli/php  mydaemon.php start

 

输出如下信息:

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  mydaemon.php stop

输出信息如下:

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 = "";


    private $_signalHandlerFuns = array();


public function __construct($configs = array())

{   

   //load config

        if (is_array($configs))

            $this->setConfigs($configs);

}

    public function _checkRequirement()

    {

        //check if pctnl loaded

        if (!extension_loaded('pcntl'))

            throw new KalonDaemonException("daemon needs support of pcntl extension, please enable it.");


        //check sapi name,only for cli    

        if ('cli' != php_sapi_name())

            throw new KalonDaemonException("daemon only works in cli sapi.");    

    }

   

public function setConfigs($configs)

{

        foreach ((array) $configs as $item => $config) {

            switch ($item) {

                case "pidFilePath":

                    $this->setPidFilePath($config);

                    break;

                case "pidFileName":

                    $this->setPidFileName($config);

                    break;

                 case "verbose":

                    $this->setVerbose($config);

                    break;

                 case "singleton":

                    $this->setSingleton($config);

                    break;

                 case "closeStdHandle";

                    $this->setCloseStdHandle($config); 

                    break;                                        

                default:

                    throw new KalonDaemonException("Unknown config item {$item}");

                    break;

            }

   }

}

public function setPidFilePath($path)

{

   if (empty($path))

       return false;

       

   if(!is_dir($path))

       if (!mkdir($path, 0777))

           throw new KalonDaemonException("setPidFilePath: cannnot make dir {$path}.");


   $this->_pidFilePath = rtrim($path, "/");

   return true;    

}

public function getPidFilePath()

{

   return $this->_pidFilePath;

}

public function setPidFileName($name)

{

   if (empty($name))

       return false;

   

   $this->_pidFileName = trim($name);

   return true;    

}

public function getPidFileName()

{

   return $this->_pidFileName;

}

public function setVerbose($open = true)

{

   $this->_verbose = (boolean) $open;

   return true;

}

public function getVerbose()

{

   return $this->_verbose;

}

public function setSingleton($singleton = true)

{

   $this->_singleton = (boolean) $singleton;

   return true;

}

public function getSingleton()

{

   return $this->_singleton;

}

public function setCloseStdHandle($close = true)

{

   $this->_closeStdHandle = (boolean) $close;

   return true;

}

public function getCloseStdHandle()

{

   return $this->_closeStdHandle;

}

public function start()

{

   //this line used to put in the __construct,for some reason I move it here.

   $this->_checkRequirement();

   

   //do daemon

$this->_daemonize();

 

        //default handler for stop

   if(!pcntl_signal(SIGTERM,  array($this,"signalHandler")))

    throw new KalonDaemonException("Cannot setup signal handler for signo {$signo}");  

        

        

   //close file handle STDIN STDOUT STDERR

   //notic!!!This makes no use in PHP4 and some early version of PHP5

   //if we close these handle without dup to /dev/null,php process will die 

   //when operating on them.

   if ($this->_closeStdHandle) {

       //fclose(STDIN);

       //fclose(STDOUT);

       //fclose(STDERR);

   }

   

   return true;

}

public function stop($force = false)

{

   if ($force) 

       $signo = SIGKILL; //kill -9

   else  

       $signo = SIGTERM; //kill 

           

   //only use in singleton model    

   if (!$this->_singleton)

       throw new KalonDaemonException("'stop' only use in singleton model.");

          

if (false === ($pid = $this->_getPidFromFile()))

   throw new KalonDaemonException("daemon is not running,cannot stop.");

if (!posix_kill($pid, $signo)) {

            throw new KalonDaemonException("Cannot send signal $signo to daemon.");

}

$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)) {

   foreach ($signFuns as $fun) {

           call_user_func($fun);

       }

}

//default action

switch ($signo) {

case SIGTERM:

exit;

break;

default:

// handle all other signals

}

}

public function addSignalHandler($signo, $fun)

{

   if (is_string($fun)) {

    if (!function_exists($fun)) {

    throw new KalonDaemonException("handler function {$fun} not exists");

    }

   }elseif (is_array($fun)) {

    if (!@method_exists($fun[0], $fun[1])) {

                throw new KalonDaemonException("handler method not exists");

    }    

   } else {

       throw new KalonDaemonException("error handler.");

   }


   if(!pcntl_signal($signo,  array($this,"signalHandler")))

       throw new KalonDaemonException("Cannot setup signal handler for signo {$signo}");


   $this->_signalHandlerFuns[$signo][] = $fun;

   return $this;    

}

public function sendSignal($signo)

{

if (false === ($pid = $this->_getPidFromFile()))

   throw new KalonDaemonException("daemon is not running,cannot send signal.");

if (!posix_kill($pid, $signo)) {

            throw new KalonDaemonException("Cannot send signal $signo to daemon.");

}

//$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)

   return false;

   

if (false === ($active = @pcntl_getpriority($pid)))

   return false;

        else

            return true;

}

private function _daemonize()

{

//single model, first check if running

if ($this->_singleton) {

   $isRunning  = $this->_checkRunning();

            if ($isRunning) 

   throw new KalonDaemonException("Daemon already running");

}

//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)

   $this->_logPid();

return $this->_pid;

}

private function _getPidFromFile()

{

   //if is set

   if ($this->_pid)

       return (int)$this->_pid;

       

$pidFile = $this->_pidFilePath . "/" . $this->_pidFileName;

//no pid file,it's the first time of running

if (!file_exists($pidFile))

   return false;

   

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)

   return false;

//get exe file path from pid

   switch(strtolower(PHP_OS))

{

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")

   return false;

 

   $selfFile = "";

   $sapi = php_sapi_name();

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)

   return false;

   else 

       return true;

}

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 _unlinkPidFile()

    {

        $pidFile = $this->_pidFilePath . '/' . $this->_pidFileName;

        return @unlink($pidFile);

    }

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))) {

            //throw new KalonDaemonException("Cannot read link file {$strProcExeFile}");

            return false;

}

        

return $strLink;

}

private function _getLinuxProcExe($pid)

{

$strProcExeFile = "/proc/" . $pid . "/exe";

if (false === ($strLink = @readlink($strProcExeFile))) {

           //throw new KalonDaemonException("Cannot read link file {$strProcExeFile}");

            return false; 

}

        

return $strLink;

}

private function _getFreebsdProcArgs($pid)

{

return $this->_getLinuxProcArgs($pid);

}

private function _getLinuxProcArgs($pid)

{

$strProcCmdlineFile = "/proc/" . $pid . "/cmdline";

if (!$fp = @fopen($strProcCmdlineFile, "r")) {

   throw new KalonDaemonException("Cannot open file {$strProcCmdlineFile} for read");

   

}

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) {

   throw new KalonDaemonException("Invalid content in {$strProcCmdlineFile}"); 

}

return trim($arrTemp[1]);

}

private function _getLinuxProcCwd($pid)

{

$strProcExeFile = "/proc/" . $pid . "/cwd";

if (false === ($strLink = @readlink($strProcExeFile))) {

            throw new KalonDaemonException("Cannot read link file {$strProcExeFile}");

}

return $strLink;

}

private function _out($str)

{

   if ($this->_verbose) {

       fwrite(STDOUT, $str . "/n");

   

   return true;    

}

}


class KalonDaemonException extends Exception 

{

    

}

?>


0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有