php高階應用之程序控制及程序間通訊
很少有用php寫服務的,然而有些場景又要求能有一個這樣的伺服器程式,它能夠與php無縫結合,並且提供高可靠靠效能的服務,並且提供現有架構所沒有的一些高階特性,例如支援自定義協議,支援長連線等等。PPM(PHP-Process-Manager)是我用PHP開發的一款程序管理框架,集成了socket服務功能,無需安裝nginx、apache、php-fpm,只需要安裝php-cli即可。支援libevent事件輪詢庫,支援服務平滑重啟,支援磁碟檔案監控及自動更新,支援各種協議包括自定義協議,最主要的由於是用php寫的,它能與php無縫結合,非常適合用php寫後端業務邏輯。
下面我講下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
等等..
具體使用哪種,看實際需要而定。