很少有用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上下載下傳