天天看點

php進階應用之程序控制及程序間通訊

很少有用php寫服務的,然而有些場景又要求能有一個這樣的伺服器程式,它能夠與php無縫結合,并且提供高可靠靠性能的服務,并且提供現有架構所沒有的一些進階特性,例如支援自定義協定,支援長連接配接等等。PPM(PHP-Process-Manager)是我用PHP開發的一款程序管理架構,內建了socket服務功能,無需安裝nginx、apache、php-fpm,隻需要安裝php-cli即可。支援libevent事件輪詢庫,支援服務平滑重新開機,支援磁盤檔案監控及自動更新,支援各種協定包括自定義協定,最主要的由于是用php寫的,它能與php無縫結合,非常适合用php寫後端業務邏輯。

代碼已經開源,更名為WorkerMan,代碼在www.workerman.net,或者在https://github.com/walkor上下載下傳

下面我講下PPM程序控制與管理的做法

為了充分發揮伺服器多核的優勢,socket服務都會采用多線程及多程序的模型對外提供服務。用php寫服務必然涉及到程序管理或這線程管理方面的東西,由于PPM使用的是多程序模型,這裡主要講下php程序控制與管理方面的内容。

1、如何成為daemon程式?

2、如何實作多程序?

3、如何監控子程序是否退出?

4、程序間如何通信?

以下都是在linux環境中(windows不支援php多程序),并且是在php-cli模式下。

1、如何成為daemon程式

廢話不多說,直接上代碼

        // 設定umask
        umask(0);
        // fork一次
        $pid = pcntl_fork();
        if(-1 == $pid)
        {
            // 出錯退出
            exit("Daemonize fail ,can not fork");
        }
        elseif($pid > 0)
        {
            // 父程序,退出
            exit(0);
        }
        // 子程序使之成為session leader
        if(-1 == posix_setsid())
        {
            // 出錯退出
            exit("Daemonize fail ,setsid fail");
        }
        // 現在已經是daemon程序了
           

2、如何實作多程序

類似上面使用pcntl_fork函數,也不多說,看代碼

$pid = pcntl_fork();
        if($pid > 0)
        {
            // 父程序
            
        }
        elseif(0 == $pid)
        {
             // 子程序
        }
        else
        {
             // 出錯
        }
           

3、如何監控子程序是否退出

php監控子程序退出有幾種辦法

1、監聽SIGCHLD信号

在linux系統中,一個程序終止或者停止時,它的父程序會收到一個SIGCHLD信号,在php中可以用

pcntl_sigwaitinfo、pcntl_sigtimedwait、pcntl_signal等函數都檢測到此信号。父收到SIGCHLD信号,說明有子程序退出了。要注意的是信号可能會重疊,當有多個SIGCHLD信号到達父程序時,父程序可能隻檢測到一個SIGCHLD信号。需要循環用pcntl_wait/pcntl_waitpid函數檢測到底有幾個子程序退出以及退出的狀态。

一個監控SIGCHLD信号的示例

pcntl_sigtimedwait(array(SIGCHLD), $siginfo);
while(($pid = pcntl_waitpid(-1, $status, WUNTRACED | WNOHANG)) != 0){
    // 退出的子程序pid
    if($pid>0){
        // pid為$pid的子程序退出了,這裡可以重新pcntl_fork一個新的子程序
    }
    else{
        // 出錯了
    }
}
           

上面這個例子程序會阻塞在pcntl_sigtimedwait上,同時我們可以用pcntl_sigtimedwait監聽更多的信号,例如同時監聽SIGINT終止信号來實作整個服務的停止,同時監聽SIGHUP來實作服務的平滑重新開機等等

2、直接調用pcntl_wait/pcntl_waitpid監控

while(($pid = pcntl_waitpid(-1, $status, WUNTRACED)) != 0){
    // 退出的子程序pid
    if($pid>0){
        // pid為$pid的子程序退出了,這裡可以重新pcntl_fork一個新的子程序
    }
    else{
        // 出錯了
    }
}
           

這裡和上面的例子少了一個pnntl_sigtimedwait調用,直接使用pcntl_waitpid,注意pcntl_waitpid第三個參數沒有傳WNOHANG,則整個程序會阻塞在pcntl_waitpid上。

3、在程序間建立socket或者管道

在程序間建立socket或者管道,然後用select等IO複用技術監聽socket或者管道可讀,如果可讀但是沒有讀出資料,那麼說明程序間的socket或者管道已經斷開了,很可能對方程序已經退出了。

// 建立管道
$channel = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
stream_set_blocking($channel[0], 0);
stream_set_blocking($channel[1], 0);
// 建立子程序
$pid = pcntl_fork();
// 父程序
if($pid > 0)
{
        fclose($channel[1]);
        $write = $e = null;
        while(1)
        {
            $read = array($channel[0]);
            if(@stream_select($read, $write, $e, 1))
            {
                 foreach($read as $channel_to_check)
                 {
                       if('' == fread($channel_to_check))
                       {
                             // $channel_to_check(這裡是channel[0])對應程序可能退出了
                       }
                  }
            }
        }
}
// 子程序
elseif($pid === 0)
{
        fclose($channel[0]);
        
}
           

上面的例子整個程序會阻塞在stream_select調用上面。建立socket監聽可讀事件,不局限與父子程序間監控程序退出,任何程序間建立連結後,都可以實作監控程序退出事件,而且程序間可以通過這個連結實作程序間通訊。

4、定時發送信号0檢測

while(1)
{
    // 使用posix_kill需要目前程序所有者與被檢測$pid所有者是同一個或者目前程序所有者擁有足夠的權限
    if(!posix_kill($pid, 0))
    {
         // 程序$pid已經退出了
    }
    sleep(1);
}
           

這個方法最簡單,隻要有權限,它可以檢測任何程序是否存活。缺點是不能及時的檢測到程序退出。

4、程序間如何通信

PHP可用的程序間通訊方法很多

1、共享記憶體 shm_*、shmop_*   // 注意多程序寫的時候要考慮互斥

2、消息隊列msg_*                         // 直接使用系統的消息隊列,簡單高效,不用考慮互斥問題

3、信号量sem_*                             // 用于互斥使用某個資源

4、管道 PIPE管道、全雙工管道、FIFO命名管道

5、socket 命名、無名、unix socket                      

等等..

具體使用哪種,看實際需要而定。

代碼已經開源,更名為WorkerMan,代碼在www.workerman.net,或者在https://github.com/walkor上下載下傳