守护进程以及PHP的实现

来源:互联网 发布:免费棋牌源码 编辑:程序博客网 时间:2024/06/05 16:18

周末在家无聊,看了下Linux守护进程(Daemon),以及实现的过程,然后用PHP实现了下,感觉这玩意确实对于一些应用场景很有用。结合PHP做一下笔记,如有错误请指正。先的熟悉一些名词。
守护进程:Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

创建一个守护进程大体这样:
1 fork子进程,父进程退出
为避免挂起控制终端将Daemon放入后台执行,方法是在进程中调用fork使父进程终止,我们所有后续工作都在子进程中完成。

01<?php
02$pid= pcntl_fork();
03//父进程和子进程都会执行下面代码
04if($pid== -1) {
05    //错误处理:创建子进程失败时返回-1.
06     die('could not fork');
07}elseif ($pid) {
08     //父进程会得到子进程号,所以这里是父进程执行的逻辑
09     pcntl_wait($status);//等待子进程中断,防止子进程成为僵尸进程。
10}else{
11     //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
12}
13?>


2 在子进程中创建新会话
先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用posix_setsid();使进程成为会话组长。setsid有3个作用:让进程摆脱原会话的控制;让进程摆脱原进程组的控制;
3 改变当前目录为根目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如chdir(“/”),如果有特殊的需要,我们也可以把当前工作目录换成其他的路径,比如/tmp。

4 重设文件权限掩码
进程从父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);如果你的应用程序根本就不涉及创建新文件或是文件访问权限的设定,这一步不是必须的。

5 关闭文件描述符
同文件权限掩码一样,新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不被我们的daemon进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。文件描述符为0、1和2的三个文件,输入、输出和报错这三个文件也需要被关闭。在PHP中只需要fclose就可以了。

1fclose(STDIN);
2fclose(STDOUT);
3fclose(STDERR);

6 守护进程退出,处理SIGCHLD信号
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。

01<?php
02//使用ticks需要PHP 4.3.0以上版本
03declare(ticks = 1);
04 
05//信号处理函数
06functionsig_handler($signo)
07{
08 
09     switch($signo) {
10         caseSIGTERM:
11             // 处理SIGTERM信号
12             exit;
13             break;
14         caseSIGHUP:
15             //处理SIGHUP信号
16             break;
17         caseSIGUSR1:
18             echo"Caught SIGUSR1...\n";
19             break;
20         default:
21             // 处理所有其他信号
22     }
23 
24}
25 
26echo"Installing signal handler...\n";
27 
28//安装信号处理器
29pcntl_signal(SIGTERM,"sig_handler");
30pcntl_signal(SIGHUP, "sig_handler");
31pcntl_signal(SIGUSR1,"sig_handler");
32 
33// 或者在PHP 4.3.0以上版本可以使用对象方法
34// pcntl_signal(SIGUSR1, array($obj, "do_something");
35 
36echo"Generating signal SIGTERM to self...\n";
37 
38//向当前进程发送SIGUSR1信号
39posix_kill(posix_getpid(), SIGUSR1);
40 
41echo"Done\n"
42?>

下面写了个简单的测试脚本,代码略搓:

001<?php
002 
003classDeamon {
004 
005    private$_pidFile;
006    private$_jobs = array();
007    private$_infoDir;
008 
009    publicfunction __construct($dir= '/tmp') {
010        $this->_setInfoDir($dir);
011        $this->_pidFile = rtrim($this->_infoDir,'/') . '/'. __CLASS__. '_pid.log';
012        $this->_checkPcntl();
013    }
014 
015    privatefunction _demonize() {
016        if(php_sapi_name() != 'cli') {
017            die('Should run in CLI');
018        }
019 
020        $pid= pcntl_fork();
021        if($pid< 0) {
022            die("Can't Fork!");
023        }elseif ($pid> 0) {
024            exit();
025        }
026 
027        if(posix_setsid() === -1) {
028            die('Could not detach');
029        }
030 
031        chdir('/');
032        umask(0);
033        $fp= fopen($this->_pidFile,'w')ordie("Can't create pid file");
034        fwrite($fp, posix_getpid());
035        fclose($fp);
036        if(!empty($this->_jobs)) {
037            foreach($this->_jobsas$job) {
038                if(!empty($job['argv'])) {
039                    call_user_func($job['function'],$job['argv']);
040                }else{
041                    call_user_func($job['function']);
042                }
043            }
044        }
045        return;
046    }
047 
048    privatefunction _setInfoDir($dir= null) {
049        if(is_dir($dir)) {
050            $this->_infoDir = $dir;
051        }else{
052            $this->_infoDir = __DIR__;
053        }
054    }
055 
056    privatefunction _checkPcntl() {
057        !function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
058    }
059 
060    privatefunction _getPid() {
061        if(!file_exists($this->_pidFile)) {
062            return0;
063        }
064 
065        $pid= intval(file_get_contents($this->_pidFile));
066        if(posix_kill($pid, SIG_DFL)) {
067            return$pid;
068        }else{
069            unlink($this->_pidFile);
070            return0;
071        }
072    }
073 
074    privatefunction _message($message) {
075        printf("%s  %d  %d  %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getpid(), posix_getppid(), $message);
076    }
077 
078    publicfunction start() {
079        if($this->_getPid() > 0) {
080            $this->_message('Running');
081        }else{
082            $this->_demonize();
083            $this->_message('Start');
084        }
085    }
086 
087    publicfunction stop() {
088        $pid= $this->_getPid();
089        if($pid> 0) {
090            posix_kill($pid, SIGTERM);
091            unlink($this->_pidFile);
092            echo'Stoped' . PHP_EOL;
093        }else{
094            echo"Not Running" . PHP_EOL;
095        }
096    }
097 
098    publicfunction status() {
099        if($this->_getPid() > 0) {
100            $this->_message('Is Running');
101        }else{
102            echo'Not Running' . PHP_EOL;
103        }
104    }
105 
106    publicfunction addJobs($jobs= array()) {
107 
108        if(!isset($jobs['function']) || empty($jobs['function'])) {
109            $this->_message('Need function param');
110        }
111 
112        if(!isset($jobs['argv']) || empty($jobs['argv'])) {
113 
114            $jobs['argv'] = "";
115        }
116 
117        $this->_jobs[] = $jobs;
118    }
119 
120    publicfunction run($argv) {
121        $param= is_array($argv) && count($argv) == 2 ? $argv[1] : null;
122        switch($param) {
123            case'start':
124                $this->start();
125                break;
126            case'stop':
127                $this->stop();
128                break;
129            case'status':
130                $this->status();
131                break;
132            default:
133                echo"Argv start|stop|status " . PHP_EOL;
134                break;
135        }
136    }
137 
138}
139 
140$deamon= newDeamon('');
141$deamon->addJobs(array('function'=> 'test','argv'=> 'Go'));
142$deamon->run($argv);
143 
144functiontest($param) {
145    $i= 0;
146    while(TRUE) {
147        echo'Now is ',$param. PHP_EOL;
148        $i++;
149        sleep(5);
150    }
151}

在CLI下面直接执行可以查看结果

1php deamon.php start #开启
2php deamon.php status #查看状态
3php deamon.php stop #停止
0 0
原创粉丝点击