php多進(jìn)程--守護(hù)進(jìn)程的實(shí)現(xiàn)

守護(hù)進(jìn)程(Daemon)是運(yùn)行在后臺(tái)的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。守護(hù)進(jìn)程是一種很有用的進(jìn)程。php也可以實(shí)現(xiàn)守護(hù)進(jìn)程的功能。

1、基本概念
進(jìn)程
每個(gè)進(jìn)程都有一個(gè)父進(jìn)程,子進(jìn)程退出,父進(jìn)程能得到子進(jìn)程退出的狀態(tài)。

進(jìn)程組
每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組,每個(gè)進(jìn)程組都有一個(gè)進(jìn)程組號(hào),該號(hào)等于該進(jìn)程組組長(zhǎng)的PID

2、守護(hù)編程要點(diǎn)

  1. 在后臺(tái)運(yùn)行。
    為避免掛起控制終端將Daemon放入后臺(tái)執(zhí)行。方法是在進(jìn)程中調(diào)用fork使父進(jìn)程終止,讓Daemon在子進(jìn)程中后臺(tái)執(zhí)行。 if($pid=pcntl_fork()) exit(0);//是父進(jìn)程,結(jié)束父進(jìn)程,子進(jìn)程繼續(xù)
  2. 脫離控制終端,登錄會(huì)話和進(jìn)程組
    有必要先介紹一下Linux中的進(jìn)程與控制終端,登錄會(huì)話和進(jìn)程組之間的關(guān)系:進(jìn)程屬于一個(gè)進(jìn)程組,進(jìn)程組號(hào)(GID)就是進(jìn)程組長(zhǎng)的進(jìn)程號(hào)(PID)。登錄會(huì)話可以包含多個(gè)進(jìn)程組。這些進(jìn)程組共享一個(gè)控制終端。這個(gè)控制終端通常是創(chuàng)建進(jìn)程的登錄終端。 控制終端,登錄會(huì)話和進(jìn)程組通常是從父進(jìn)程繼承下來(lái)的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點(diǎn)的基礎(chǔ)上,調(diào)用setsid()使進(jìn)程成為會(huì)話組長(zhǎng): posix_setsid();
    說(shuō)明:當(dāng)進(jìn)程是會(huì)話組長(zhǎng)時(shí)setsid()調(diào)用失敗。但第一點(diǎn)已經(jīng)保證進(jìn)程不是會(huì)話組長(zhǎng)。setsid()調(diào)用成功后,進(jìn)程成為新的會(huì)話組長(zhǎng)和新的進(jìn)程組長(zhǎng),并與原來(lái)的登錄會(huì)話和進(jìn)程組脫離。由于會(huì)話過(guò)程對(duì)控制終端的獨(dú)占性,進(jìn)程同時(shí)與控制終端脫離。
  3. 禁止進(jìn)程重新打開(kāi)控制終端
    現(xiàn)在,進(jìn)程已經(jīng)成為無(wú)終端的會(huì)話組長(zhǎng)。但它可以重新申請(qǐng)打開(kāi)一個(gè)控制終端??梢酝ㄟ^(guò)使進(jìn)程不再成為會(huì)話組長(zhǎng)來(lái)禁止進(jìn)程重新打開(kāi)控制終端: if($pid=pcntl_fork()) exit(0);//結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會(huì)話組長(zhǎng))
  4. 關(guān)閉打開(kāi)的文件描述符
    進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開(kāi)的文件描述符。如不關(guān)閉,將會(huì)浪費(fèi)系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無(wú)法卸下以及引起無(wú)法預(yù)料的錯(cuò)誤。按如下方法關(guān)閉它們:
    fclose(STDIN),fclose(STDOUT),fclose(STDERR)關(guān)閉標(biāo)準(zhǔn)輸入輸出與錯(cuò)誤顯示。
  5. 改變當(dāng)前工作目錄
    進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對(duì)于需要轉(zhuǎn)儲(chǔ)核心,寫運(yùn)行日志的進(jìn)程將工作目錄改變到特定目錄如chdir("/")
  6. 重設(shè)文件創(chuàng)建掩模
    進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩模。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取位。為防止這一點(diǎn),將文件創(chuàng)建掩模清除:umask(0); 給予操作文件的最大權(quán)限。
  7. 處理SIGCHLD信號(hào)
    處理SIGCHLD信號(hào)并不是必須的。但對(duì)于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請(qǐng)求到來(lái)時(shí)生成子進(jìn)程處理請(qǐng)求。如果父進(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下可以簡(jiǎn)單地將SIGCHLD信號(hào)的操作設(shè)為SIG_IGN。 signal(SIGCHLD,SIG_IGN);
    這樣,內(nèi)核在子進(jìn)程結(jié)束時(shí)不會(huì)產(chǎn)生僵尸進(jìn)程。這一點(diǎn)與BSD4不同,BSD4下必須顯式等待子進(jìn)程結(jié)束才能釋放僵尸進(jìn)程。關(guān)于信號(hào)的問(wèn)題請(qǐng)參考Linux 信號(hào)說(shuō)明列表

3、實(shí)例

<?php
/**
*@author tengzhaorong@gmail.com
*@date 2013-07-25
* 后臺(tái)腳本控制類
*/
class DaemonCommand{
 
    private $info_dir="/tmp";
    private $pid_file="";
    private $terminate=false; //是否中斷
    private $workers_count=0;
    private $gc_enabled=null;
    private $workers_max=8; //最多運(yùn)行8個(gè)進(jìn)程
 
    public function __construct($is_sington=false,$user='nobody',$output="/dev/null"){
 
            $this->is_sington=$is_sington; //是否單例運(yùn)行,單例運(yùn)行會(huì)在tmp目錄下建立一個(gè)唯一的PID
            $this->user=$user;//設(shè)置運(yùn)行的用戶 默認(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);
        }
        //信號(hào)處理
        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下面運(yùn)行
        if (php_sapi_name() != "cli"){
            die("only run in command line mode\n");
        }
 
        // 只能單例運(yù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è)置新會(huì)話組長(zhǎng),脫離終端
 
        if (pcntl_fork() != 0){ //是第一子進(jìn)程,結(jié)束第一子進(jìn)程   
            exit();
        }
 
        chdir("/"); //改變工作目錄
 
        $this->setUser($this->user) or die("cannot change owner");
 
        //關(guān)閉打開(kāi)的文件描述符
        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();
        }
 
    }
    //--檢測(cè)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è)置運(yùn)行的用戶
    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;
 
    }
    //信號(hào)處理函數(shù)
    public function signalHandler($signo){
 
        switch($signo){
            //用戶自定義信號(hào)
            case SIGUSR1: //busy
            if ($this->workers_count < $this->workers_max){
                $pid = pcntl_fork();
                if ($pid > 0){
                    $this->workers_count ++;
                }
            }
            break;
            //子進(jìn)程結(jié)束信號(hào)
            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;
        }
 
    }
    /**
    *開(kāi)始開(kāi)啟進(jìn)程
    *$count 準(zhǔn)備開(kāi)啟的進(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){//控制開(kāi)啟的進(jìn)程數(shù)量
                $pid=pcntl_fork();
            }
 
            if($pid>0){
                $this->workers_count++;
            }elseif($pid==0){
                // 這個(gè)符號(hào)表示恢復(fù)系統(tǒng)對(duì)信號(hào)的默認(rèn)處理
                pcntl_signal(SIGTERM, SIG_DFL);
                pcntl_signal(SIGCHLD, SIG_DFL);
                if(!empty($this->jobs[$this->workers_count])){
                    while($this->jobs[$this->workers_count]['runtime']){
                        if(!empty($this->jobs[$this->workers_count]['argv'])){
                            call_user_func($this->jobs[$this->workers_count]['function'],$this->jobs[$this->workers_count]['argv']);
                        }else{
                            call_user_func($this->jobs[$this->workers_count]['function']);
                        }
                        $this->jobs[$this->workers_count]['runtime']--;
                        sleep(2);
                    }
                    exit();
                }
                return;
            }else{
                sleep(2);
            }
        }
 
        $this->mainQuit();
        exit(0); 
    }
 
    //整個(gè)進(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);
    }
 
    // 添加工作實(shí)例
    public function setJobs($jobs=array()){
        if(!empty($jobs)){
            foreach($jobs as $key=>$value){
                if(!isset($value['argv'])||empty($value['argv'])){
                    $value['argv']="";
                }
                if(!isset($value['runtime'])||empty($value['runtime'])){
                    $value['runtime']=1;
                }
                if(!isset($value['function'])||empty($value['function'])){
                    $this->log("你必須添加運(yùn)行的函數(shù)!");
                    exit;
                }
                $this->jobs[$key+1] = $value;
            }
        }
    }
    //日志處理
    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);//開(kāi)啟2個(gè)子進(jìn)程工作
work();
 

//調(diào)用方法2
$daemon=new DaemonCommand(true);
$daemon->daemonize();
$daemon->setJobs([
['function'=>'work','argv'=>'','runtime'=>3],['function'=>'work','argv'=>'','runtime'=>3]
]);//function 要運(yùn)行的函數(shù),argv運(yùn)行函數(shù)的參數(shù),runtime運(yùn)行的次數(shù)
$daemon->start(2);//開(kāi)啟2個(gè)子進(jìn)程工作  每個(gè)子進(jìn)程處理相應(yīng)的任務(wù)號(hào)
 
//具體功能的實(shí)現(xiàn)
function work(){
      echo "測(cè)試1";
}
?>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容