如何寫php守護(hù)進(jìn)程(Daemon)
守護(hù)進(jìn)程(Daemon)是運行在后臺的一種特殊進(jìn)程。它獨立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。守護(hù)進(jìn)程是一種很有用的進(jìn)程。php也可以實現(xiàn)守護(hù)進(jìn)程的功能。
一、基本概念
進(jìn)程: 每個進(jìn)程都有一個父進(jìn)程,子進(jìn)程退出,父進(jìn)程能得到子進(jìn)程退出的狀態(tài)。
進(jìn)程組:每個進(jìn)程都屬于一個進(jìn)程組,每個進(jìn)程組都有一個進(jìn)程組號,該號等于該進(jìn)程組組長的PID
二、守護(hù)編程要點
1. 在后臺運行
為避免掛起控制終端將Daemon放入后臺執(zhí)行。方法是在進(jìn)程中調(diào)用fork使父進(jìn)程終止,讓Daemon在子進(jìn)程中后臺執(zhí)行。 if($pid=pcntl_fork()) exit(0);//是父進(jìn)程,結(jié)束父進(jìn)程,子進(jìn)程繼續(xù)
2. 脫離控制終端,登錄會話和進(jìn)程組
有必要先介紹一下Linux中的進(jìn)程與控制終端,登錄會話和進(jìn)程組之間的關(guān)系:進(jìn)程屬于一個進(jìn)程組,進(jìn)程組號(GID)就是進(jìn)程組長的進(jìn)程號(PID)。登錄會話可以包含多個進(jìn)程組。這些進(jìn)程組共享一個控制終端。這個控制終端通常是創(chuàng)建進(jìn)程的登錄終 端。 控制終端,登錄會話和進(jìn)程組通常是從父進(jìn)程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎(chǔ)上,調(diào)用setsid()使進(jìn)程成為會話組長: posix_setsid();
說明:當(dāng)進(jìn)程是會話組長時setsid()調(diào)用失敗。但第一點已經(jīng)保證進(jìn)程不是會話組長。setsid()調(diào)用成功后,進(jìn)程成為新的會話組長和新的進(jìn)程組長,并與原來的登錄會話和進(jìn)程組脫離。由于會話過程對控制終端的獨占性,進(jìn)程同時與控制終端脫離。
3. 禁止進(jìn)程重新打開控制終端
現(xiàn)在,進(jìn)程已經(jīng)成為無終端的會話組長。但它可以重新申請打開一個控制終端。可以通過使進(jìn)程不再成為會話組長來禁止進(jìn)程重新打開控制終端: if($pid=pcntl_fork()) exit(0);//結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會話組長)
4. 關(guān)閉打開的文件描述符
進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開的文件描述符。如不關(guān)閉,將會浪費系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無法卸下以及引起無法預(yù)料的錯誤。按如下方法關(guān)閉它們:
fclose(STDIN),fclose(STDOUT),fclose(STDERR)關(guān)閉標(biāo)準(zhǔn)輸入輸出與錯誤顯示。
5. 改變當(dāng)前工作目錄
進(jìn)程活動時,其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對于需要轉(zhuǎn)儲核心,寫運行日志的進(jìn)程將工作目錄改變到特定目錄如chdir("/")
6. 重設(shè)文件創(chuàng)建掩模
進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩模。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取位。為防止這一點,將文件創(chuàng)建掩模清除:umask(0);
7. 處理SIGCHLD信號
處理SIGCHLD信號并不是必須的。但對于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請求到來時生成子進(jìn)程處理請求。如果父進(jìn)程不等待子進(jìn)程結(jié)束,子進(jìn)程將成為僵尸進(jìn)程(zombie)從而占用系統(tǒng)資源。如果父進(jìn)程等待子進(jìn)程結(jié)束,將增加父進(jìn)程的負(fù)擔(dān),影 響服務(wù)器進(jìn)程的并發(fā)性能。在Linux下可以簡單地將SIGCHLD信號的操作設(shè)為SIG_IGN。 signal(SIGCHLD,SIG_IGN);
這樣,內(nèi)核在子進(jìn)程結(jié)束時不會產(chǎn)生僵尸進(jìn)程。這一點與BSD4不同,BSD4下必須顯式等待子進(jìn)程結(jié)束才能釋放僵尸進(jìn)程。關(guān)于信號的問題請參考Linux 信號說明列表
三、實例
<?php
* 后臺腳本控制類
*/
class DaemonCommand{
private $info_dir="/tmp";
private $pid_file="";
private $terminate=false; //是否中斷
private $workers_count=0;
private $gc_enabled=null;
private $workers_max=8; //最多運行8個進(jìn)程
public function __construct($is_sington=false,$user='nobody',$output="/dev/null"){
$this->is_sington=$is_sington; //是否單例運行,單例運行會在tmp目錄下建立一個唯一的PID
$this->user=$user;//設(shè)置運行的用戶 默認(rèn)情況下nobody
$this->output=$output; //設(shè)置輸出的地方
$this->checkPcntl();
}
//檢查環(huán)境是否支持pcntl支持
public function checkPcntl(){
if ( ! function_exists('pcntl_signal_dispatch')) {
// PHP < 5.3 uses ticks to handle signals instead of pcntl_signal_dispatch
// call sighandler only every 10 ticks
declare(ticks = 10);
}
// Make sure PHP has support for pcntl
if ( ! function_exists('pcntl_signal')) {
$message = 'PHP does not appear to be compiled with the PCNTL extension. This is neccesary for daemonization';
$this->_log($message);
throw new Exception($message);
}
//信號處理
pcntl_signal(SIGTERM, array(__CLASS__, "signalHandler"),false);
pcntl_signal(SIGINT, array(__CLASS__, "signalHandler"),false);
pcntl_signal(SIGQUIT, array(__CLASS__, "signalHandler"),false);
// Enable PHP 5.3 garbage collection
if (function_exists('gc_enable'))
{
gc_enable();
$this->gc_enabled = gc_enabled();
}
}
// daemon化程序
public function daemonize(){
global $stdin, $stdout, $stderr;
global $argv;
set_time_limit(0);
// 只允許在cli下面運行
if (php_sapi_name() != "cli"){
die("only run in command line mode\n");
}
// 只能單例運行
if ($this->is_sington==true){
$this->pid_file = $this->info_dir . "/" .__CLASS__ . "_" . substr(basename($argv[0]), 0, -4) . ".pid";
$this->checkPidfile();
}
umask(0); //把文件掩碼清0
if (pcntl_fork() != 0){ //是父進(jìn)程,父進(jìn)程退出
exit();
}
posix_setsid();//設(shè)置新會話組長,脫離終端
if (pcntl_fork() != 0){ //是第一子進(jìn)程,結(jié)束第一子進(jìn)程
exit();
}
chdir("/"); //改變工作目錄
$this->setUser($this->user) or die("cannot change owner");
//關(guān)閉打開的文件描述符
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
$stdin = fopen($this->output, 'r');
$stdout = fopen($this->output, 'a');
$stderr = fopen($this->output, 'a');
if ($this->is_sington==true){
$this->createPidfile();
}
}
//--檢測pid是否已經(jīng)存在
public function checkPidfile(){
if (!file_exists($this->pid_file)){
return true;
}
$pid = file_get_contents($this->pid_file);
$pid = intval($pid);
if ($pid > 0 && posix_kill($pid, 0)){
$this->_log("the daemon process is already started");
}
else {
$this->_log("the daemon proces end abnormally, please check pidfile " . $this->pid_file);
}
exit(1);
}
//----創(chuàng)建pid
public function createPidfile(){
if (!is_dir($this->info_dir)){
mkdir($this->info_dir);
}
$fp = fopen($this->pid_file, 'w') or die("cannot create pid file");
fwrite($fp, posix_getpid());
fclose($fp);
$this->_log("create pid file " . $this->pid_file);
}
//設(shè)置運行的用戶
public function setUser($name){
$result = false;
if (empty($name)){
return true;
}
$user = posix_getpwnam($name);
if ($user) {
$uid = $user['uid'];
$gid = $user['gid'];
$result = posix_setuid($uid);
posix_setgid($gid);
}
return $result;
}
//信號處理函數(shù)
public function signalHandler($signo){
switch($signo){
//用戶自定義信號
case SIGUSR1: //busy
if ($this->workers_count < $this->workers_max){
$pid = pcntl_fork();
if ($pid > 0){
$this->workers_count ++;
}
}
break;
//子進(jìn)程結(jié)束信號
case SIGCHLD:
while(($pid=pcntl_waitpid(-1, $status, WNOHANG)) > 0){
$this->workers_count --;
}
break;
//中斷進(jìn)程
case SIGTERM:
case SIGHUP:
case SIGQUIT:
$this->terminate = true;
break;
default:
return false;
}
}
/**
*開始開啟進(jìn)程
*$count 準(zhǔn)備開啟的進(jìn)程數(shù)
*/
public function start($count=1){
$this->_log("daemon process is running now");
pcntl_signal(SIGCHLD, array(__CLASS__, "signalHandler"),false); // if worker die, minus children num
while (true) {
if (function_exists('pcntl_signal_dispatch')){
pcntl_signal_dispatch();
}
if ($this->terminate){
break;
}
$pid=-1;
if($this->workers_count<$count){
$pid=pcntl_fork();
}
if($pid>0){
$this->workers_count++;
}elseif($pid==0){
// 這個符號表示恢復(fù)系統(tǒng)對信號的默認(rèn)處理
pcntl_signal(SIGTERM, SIG_DFL);
pcntl_signal(SIGCHLD, SIG_DFL);
if(!empty($this->jobs)){
while($this->jobs['runtime']){
if(empty($this->jobs['argv'])){
call_user_func($this->jobs['function'],$this->jobs['argv']);
}else{
call_user_func($this->jobs['function']);
}
$this->jobs['runtime']--;
sleep(2);
}
exit();
}
return;
}else{
sleep(2);
}
}
$this->mainQuit();
exit(0);
}
//整個進(jìn)程退出
public function mainQuit(){
if (file_exists($this->pid_file)){
unlink($this->pid_file);
$this->_log("delete pid file " . $this->pid_file);
}
$this->_log("daemon process exit now");
posix_kill(0, SIGKILL);
exit(0);
}
// 添加工作實例,目前只支持單個job工作
public function setJobs($jobs=array()){
if(!isset($jobs['argv'])||empty($jobs['argv'])){
$jobs['argv']="";
}
if(!isset($jobs['runtime'])||empty($jobs['runtime'])){
$jobs['runtime']=1;
}
if(!isset($jobs['function'])||empty($jobs['function'])){
$this->log("你必須添加運行的函數(shù)!");
}
$this->jobs=$jobs;
}
//日志處理
private function _log($message){
printf("%s\t%d\t%d\t%s\n", date("c"), posix_getpid(), posix_getppid(), $message);
}
}
//調(diào)用方法1
$daemon=new DaemonCommand(true);
$daemon->daemonize();
$daemon->start(2);//開啟2個子進(jìn)程工作
work();
//調(diào)用方法2
$daemon=new DaemonCommand(true);
$daemon->daemonize();
$daemon->addJobs(array('function'=>'work','argv'=>'','runtime'=>1000));//function 要運行的函數(shù),argv運行函數(shù)的參數(shù),runtime運行的次數(shù)
$daemon->start(2);//開啟2個子進(jìn)程工作
//具體功能的實現(xiàn)
function work(){
echo "測試1";
}
?>
以上就是關(guān)于php守護(hù)進(jìn)程的相關(guān)介紹,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
php實現(xiàn)數(shù)組中索引關(guān)聯(lián)數(shù)據(jù)轉(zhuǎn)換成json對象的方法
這篇文章主要介紹了php實現(xiàn)數(shù)組中索引關(guān)聯(lián)數(shù)據(jù)轉(zhuǎn)換成json對象的方法,基于Yii框架分析了php數(shù)組與json格式數(shù)據(jù)的轉(zhuǎn)換技巧,需要的朋友可以參考下2015-07-07
php+jquery編碼方面的一些心得(utf-8 gb2312)
在開發(fā)php與jquery的過程中,需要注意的一些心得,防止亂碼的出現(xiàn)。2010-10-10
實現(xiàn)php刪除鏈表中重復(fù)的結(jié)點
在本篇文章中,我們給大家?guī)砹岁P(guān)于php刪除鏈表中重復(fù)的結(jié)點的相關(guān)知識點內(nèi)容以及相關(guān)代碼,有興趣的朋友們參考下。2018-09-09
解析php做推送服務(wù)端實現(xiàn)ios消息推送
本篇文章是對php做推送服務(wù)端實現(xiàn)ios消息推送的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07
PHP使用trim函數(shù)去除字符串左右空格及特殊字符實例
這篇文章主要介紹了PHP使用trim函數(shù)去除字符串左右空格及特殊字符的用法,結(jié)合實例簡單分析了trim函數(shù)不帶附加參數(shù)去除空格及使用附加參數(shù)去除指定字符的使用技巧,需要的朋友可以參考下2016-01-01

