摘要:代碼實現(xiàn)啟動啟動流程見流程,主要包括守護(hù)進(jìn)程保存注冊信號處理器創(chuàng)建多進(jìn)程這部分。模擬調(diào)度實際用實現(xiàn)捕獲信號其中,會在每次調(diào)度過程中,捕獲信號并執(zhí)行注冊的信號處理器。
首發(fā)于 樊浩柏科學(xué)院
經(jīng)過 用 PHP 玩轉(zhuǎn)進(jìn)程之一 — 基礎(chǔ) 的回顧復(fù)習(xí),我們已經(jīng)掌握了進(jìn)程的基礎(chǔ)知識,現(xiàn)在可以嘗試用 PHP 做一些簡單的進(jìn)程控制和管理,來加深我們對進(jìn)程的理解。接下來,我將用多進(jìn)程模型實現(xiàn)一個簡單的 PHPServer,基于它你可以做任何事。
PHPServer 完整的源代碼,可前往 fan-haobai/php-server 獲取。
總流程該 PHPServer 的 Master 和 Worker 進(jìn)程主要控制流程,如下圖所示:
其中,主要涉及 3 個對象,分別為 入口腳本、Master 進(jìn)程、Worker 進(jìn)程。它們扮演的角色如下:
入口腳本:主要實現(xiàn) PHPServer 的啟動、停止、重載功能,即觸發(fā) Master 進(jìn)程start、stop、reload流程;
Master 進(jìn)程:負(fù)責(zé)創(chuàng)建并監(jiān)控 Worker 進(jìn)程。在啟動階段,會注冊信號處理器,然后創(chuàng)建 Worker;在運(yùn)行階段,會持續(xù)監(jiān)控 Worker 進(jìn)程健康狀態(tài),并接受來自入口腳本的控制信號并作出響應(yīng);在停止階段,會停止掉所有 Worker 進(jìn)程;
Worker 進(jìn)程:負(fù)責(zé)執(zhí)行業(yè)務(wù)邏輯。在被 Master 進(jìn)程創(chuàng)建后,就處于持續(xù)運(yùn)行階段,會監(jiān)聽到來自 Master 進(jìn)程的信號,以實現(xiàn)自我的停止;
整個過程,又包括 4 個流程:
流程 ① :以守護(hù)態(tài)啟動 PHPServer 時的主要流程。入口腳本會進(jìn)行 daemonize,也就是實現(xiàn)進(jìn)程的守護(hù)態(tài),此時會fork出一個 Master 進(jìn)程;Master 進(jìn)程先經(jīng)過 保存 PID、注冊信號處理器 操作,然后 創(chuàng)建 Worker 會fork出多個 Worker 進(jìn)程;
流程 ② :為 Master 進(jìn)程持續(xù)監(jiān)控的流程,過程中會捕獲入口腳本發(fā)送來的信號。主要監(jiān)控 Worker 進(jìn)程健康狀態(tài),當(dāng) Worker 進(jìn)程異常退出時,會嘗試創(chuàng)建新的 Worker 進(jìn)程以維持 Worker 進(jìn)程數(shù)量;
流程 ③ :為 Worker 進(jìn)程持續(xù)運(yùn)行的流程,過程中會捕獲 Master 進(jìn)程發(fā)送來的信號。流程 ① 中 Worker 進(jìn)程被創(chuàng)建后,就會持續(xù)執(zhí)行業(yè)務(wù)邏輯,并阻塞于此;
流程 ④ :停止 PHPServer 的主要流程。入口腳本首先會向 Master 進(jìn)程發(fā)送 SIGINT 信號,Master 進(jìn)程捕獲到該信號后,會向所有的 Worker 進(jìn)程轉(zhuǎn)發(fā) SIGINT 信號(通知所有的 Worker 進(jìn)程終止),等待所有 Worker 進(jìn)程終止退出;
在流程 ② 中,Worker 進(jìn)程被 Master 進(jìn)程fork出來后,就會 持續(xù)運(yùn)行 并阻塞于此,只有 Master 進(jìn)程才會繼續(xù)后續(xù)的流程。代碼實現(xiàn) 啟動
啟動流程見 流程 ①,主要包括 守護(hù)進(jìn)程、保存 PID、注冊信號處理器、創(chuàng)建多進(jìn)程 Worker 這 4 部分。
守護(hù)進(jìn)程首先,在入口腳本中fork一個子進(jìn)程,然后該進(jìn)程退出,并設(shè)置新的子進(jìn)程為會話組長,此時的這個子進(jìn)程就會脫離當(dāng)前終端的控制。如下圖所示:
這里使用了 2 次fork,所以最后fork的一個子進(jìn)程才是 Master 進(jìn)程,其實一次fork也是可以的。代碼如下:
protected static function daemonize() { umask(0); $pid = pcntl_fork(); if (-1 === $pid) { exit("process fork fail "); } elseif ($pid > 0) { exit(0); } // 將當(dāng)前進(jìn)程提升為會話leader if (-1 === posix_setsid()) { exit("process setsid fail "); } // 再次fork以避免SVR4這種系統(tǒng)終端再一次獲取到進(jìn)程控制 $pid = pcntl_fork(); if (-1 === $pid) { exit("process fork fail "); } elseif (0 !== $pid) { exit(0); } }
通常在啟動時增加-d參數(shù),表示進(jìn)程將運(yùn)行于守護(hù)態(tài)模式。
當(dāng)順利成為一個守護(hù)進(jìn)程后,Master 進(jìn)程已經(jīng)脫離了終端控制,所以有必要關(guān)閉標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出。如下:
protected static function resetStdFd() { global $STDERR, $STDOUT; //重定向標(biāo)準(zhǔn)輸出和錯誤輸出 @fclose(STDOUT); fclose(STDERR); $STDOUT = fopen(static::$stdoutFile, "a"); $STDERR = fopen(static::$stdoutFile, "a"); }保存PID
為了實現(xiàn) PHPServer 的重載或停止,我們需要將 Master 進(jìn)程的 PID 保存于 PID 文件中,如php-server.pid文件。代碼如下:
protected static function saveMasterPid() { // 保存pid以實現(xiàn)重載和停止 static::$_masterPid = posix_getpid(); if (false === file_put_contents(static::$pidFile, static::$_masterPid)) { exit("can not save pid to" . static::$pidFile . " "); } echo "PHPServer start