php寫守護進程(Daemon)

 守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。守護進程是一種頗有用的進程。php也能夠實現守護進程的功能。php

 

一、基本概念編程

    進程api

            每一個進程都有一個父進程,子進程退出,父進程能獲得子進程退出的狀態。服務器

    進程組併發

            每一個進程都屬於一個進程組,每一個進程組都有一個進程組號,該號等於該進程組組長的PIDapp

二、守護編程要點函數

    1. 在後臺運行。     
         爲避免掛起控制終端將Daemon放入後臺執行。方法是在進程中調用fork使父進程終止,讓Daemon在子進程中後臺執行。 if($pid=pcntl_fork()) exit(0);//是父進程,結束父進程,子進程繼續
    2. 脫離控制終端,登陸會話和進程組 
       有必要先介紹一下Linux中的進程與控制終端,登陸會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登陸會話能夠包含多個進程組。這些進程組共享一個控制終端。這個控制終端一般是建立進程的登陸終  端。 控制終端,登陸會話和進程組一般是從父進程繼承下來的。咱們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使進程成爲會話組長: posix_setsid();
        說明:當進程是會話組長時setsid()調用失敗。但第一點已經保證進程不是會話組長。setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登陸會話和進程組脫離。因爲會話過程對控制終端的獨佔性,進程同時與控制終端脫離。
    3. 禁止進程從新打開控制終端
        如今,進程已經成爲無終端的會話組長。但它能夠從新申請打開一個控制終端。能夠經過使進程再也不成爲會話組長來禁止進程從新打開控制終端: if($pid=pcntl_fork()) exit(0);//結束第一子進程,第二子進程繼續(第二子進程再也不是會話組長)
    4. 關閉打開的文件描述符
        進程從建立它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,形成進程所在的文件系統沒法卸下以及引發沒法預料的錯誤。按以下方法關閉它們:
        fclose(STDIN),fclose(STDOUT),fclose(STDERR)關閉標準輸入輸出與錯誤顯示。
    5. 改變當前工做目錄
        進程活動時,其工做目錄所在的文件系統不能卸下。通常須要將工做目錄改變到根目錄。對於須要轉儲核心,寫運行日誌的進程將工做目錄改變到特定目錄如chdir("/")
    6. 重設文件建立掩模
        進程從建立它的父進程那裏繼承了文件建立掩模。它可能修改守護進程所建立的文件的存取位。爲防止這一點,將文件建立掩模清除:umask(0);
    7. 處理SIGCHLD信號
        處理SIGCHLD信號並非必須的。但對於某些進程,特別是服務器進程每每在請求到來時生成子進程處理請求。若是父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。若是父進程等待子進程結束,將增長父進程的負擔,影  響服務器進程的併發性能。在Linux下能夠簡單地將SIGCHLD信號的操做設爲SIG_IGN。 signal(SIGCHLD,SIG_IGN);
        這樣,內核在子進程結束時不會產生殭屍進程。這一點與BSD4不一樣,BSD4下必須顯式等待子進程結束才能釋放殭屍進程。關於信號的問題請參考Linux 信號說明列表性能

 

  

三、實例測試

php] view plain copy print?
<?php  
/** 
*@author tengzhaorong@gmail.com 
*@date 2013-07-25 
* 後臺腳本控制類 
*/  
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個進程  
   
    public function __construct($is_sington=false,$user='nobody',$output="/dev/null"){  
   
            $this->is_sington=$is_sington; //是否單例運行,單例運行會在tmp目錄下創建一個惟一的PID  
            $this->user=$user;//設置運行的用戶 默認狀況下nobody  
            $this->output=$output; //設置輸出的地方  
            $this->checkPcntl();  
    }  
    //檢查環境是否支持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){ //是父進程,父進程退出  
            exit();  
        }  
   
        posix_setsid();//設置新會話組長,脫離終端  
   
        if (pcntl_fork() != 0){ //是第一子進程,結束第一子進程     
            exit();  
        }  
   
        chdir("/"); //改變工做目錄  
   
        $this->setUser($this->user) or die("cannot change owner");  
   
        //關閉打開的文件描述符  
        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是否已經存在  
    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);  
   
    }  
    //----建立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);  
    }  
   
    //設置運行的用戶  
    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;  
   
    }  
    //信號處理函數  
    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;  
            //子進程結束信號  
            case SIGCHLD:  
                while(($pid=pcntl_waitpid(-1, $status, WNOHANG)) > 0){  
                    $this->workers_count --;  
                }  
            break;  
            //中斷進程  
            case SIGTERM:  
            case SIGHUP:  
            case SIGQUIT:  
   
                $this->terminate = true;  
            break;  
            default:  
            return false;  
        }  
   
    }  
    /** 
    *開始開啓進程 
    *$count 準備開啓的進程數 
    */  
    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){  
   
                // 這個符號表示恢復系統對信號的默認處理  
                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);  
   
    }  
   
    //整個進程退出  
    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("你必須添加運行的函數!");  
        }  
   
        $this->jobs=$jobs;  
   
    }  
    //日誌處理  
    private  function _log($message){  
        printf("%s\t%d\t%d\t%s\n", date("c"), posix_getpid(), posix_getppid(), $message);  
    }  
   
}  
   
//調用方法1  
$daemon=new DaemonCommand(true);  
$daemon->daemonize();  
$daemon->start(2);//開啓2個子進程工做  
work();  
   
   
   
   
//調用方法2  
$daemon=new DaemonCommand(true);  
$daemon->daemonize();  
$daemon->addJobs(array('function'=>'work','argv'=>'','runtime'=>1000));//function 要運行的函數,argv運行函數的參數,runtime運行的次數  
$daemon->start(2);//開啓2個子進程工做  
   
//具體功能的實現  
function work(){  
      echo "測試1";  
}  
?>  
相關文章
相關標籤/搜索