下面由workerman教程欄目給大家介紹workerman源碼分析之啟動過程,希望對需要的朋友有所幫助!
workerman
版本:3.1.8(linux)
模型:gatewayWorker(Worker模型可與之類比)
注:只貼出講解部分代碼,出處以文件名形式給出,大家可自行查看
workerman最初只開發了Linux版本,win是后來增加的,基于命令行模式運行(cli)。
多進程模型
工作進程,Master、Gateway和Worker,Gateway主要用于處理IO事件,保存客戶端鏈接狀態,將數據處理請求發送給Worker等工作,Worker則是完全的業務邏輯處理,前者為IO密集型,后者為計算密集型,它們之間通過網絡通信,Gateway和Worker兩兩間注冊通信地址,所以非常方便的進行分布式部署,如果業務處理量大可以單純的增加Worker服務。
它們有一個負責監聽的父進程(Master),監聽子進程狀態,發送 signal 給子進程,接受來自終端的命令、信號等工作。父進程可以說是整個系統啟動后的入口。
啟動命令解析
既然以命令模式(cli)運行(注意與 fpm 的區別,后者處理來自網頁端的請求),就必然有一個啟動腳本解析命令,譬如說3.x版本(之前默認為daemon)新增一個 -d 參數,以表示守護進程運行,解析到該參數設置 self::$daemon = true, 隨后fork子進程以脫離當前進程組,設置進程組組長等工作。
這里有兩個非常重要的參數 $argc 和 $argc,前者表示參數個數,后者為一個數組,保存有命令的所有參數,比如:sudo php start.php start -d,$argv就是 Array( [0]=>start.php, [1]=>start, [2]=>-d ),而解析主要用到$argv。
啟動主要執行下面步驟:
1、包含自動加載器 Autoloader?,加載各 Application 下啟動文件;
2、設置?_appInitPath 根目錄;
3、解析,初始化參數,執行相應命令。
下面是具體實現(workerman/worker.php):
public?static?function?parseCommand() ????{ ????????//?檢查運行命令的參數 ????????global?$argv; ????????$start_file?=?$argv[0];? ????????//?命令 ????????$command?=?trim($argv[1]); ???????? ????????//?子命令,目前只支持-d ????????$command2?=?isset($argv[2])???$argv[2]?:?''; ???????? ????????//?檢查主進程是否在運行 ????????$master_pid?=?@file_get_contents(self::$pidFile); ????????$master_is_alive?=?$master_pid?&&?@posix_kill($master_pid,?0); ????????if($master_is_alive) ????????{ ????????????if($command?===?'start') ????????????{ ????????????????self::log("Workerman[$start_file]?is?running"); ????????????} ????????} ????????elseif($command?!==?'start'?&&?$command?!==?'restart') ????????{ ????????????self::log("Workerman[$start_file]?not?run"); ????????} ???????? ????????//?根據命令做相應處理 ????????switch($command) ????????{ ????????????//?啟動?workerman ????????????case?'start': ????????????????if($command2?===?'-d') ????????????????{ ????????????????????Worker::$daemonize?=?true; ????????????????} ????????????????break; ????????????//?顯示?workerman?運行狀態 ????????????case?'status': ????????????????exit(0); ????????????//?重啟?workerman ????????????case?'restart': ????????????//?停止?workeran ????????????case?'stop': ????????????????//?想主進程發送SIGINT信號,主進程會向所有子進程發送SIGINT信號 ????????????????$master_pid?&&?posix_kill($master_pid,?SIGINT); ????????????????//?如果?$timeout?秒后主進程沒有退出則展示失敗界面 ????????????????$timeout?=?5; ????????????????$start_time?=?time(); ????????????????while(1) ????????????????{ ????????????????????//?檢查主進程是否存活 ????????????????????$master_is_alive?=?$master_pid?&&?posix_kill($master_pid,?0); ????????????????????if($master_is_alive) ????????????????????{ ????????????????????????//?檢查是否超過$timeout時間 ????????????????????????if(time()?-?$start_time?>=?$timeout) ????????????????????????{ ????????????????????????????self::log("Workerman[$start_file]?stop?fail"); ????????????????????????????exit; ????????????????????????} ????????????????????????usleep(10000); ????????????????????????continue; ????????????????????} ????????????????????self::log("Workerman[$start_file]?stop?success"); ????????????????????//?是restart命令 ????????????????????if($command?===?'stop') ????????????????????{ ????????????????????????exit(0); ????????????????????} ????????????????????//?-d?說明是以守護進程的方式啟動 ????????????????????if($command2?===?'-d') ????????????????????{ ????????????????????????Worker::$daemonize?=?true; ????????????????????} ????????????????????break; ????????????????} ????????????????break; ????????????//?平滑重啟?workerman ????????????case?'reload': ????????????????exit; ????????} ????}
walker代碼注釋已經非常詳盡,下面有幾點細節處:
1、檢查主進程是否存活:17行的邏輯與操作,如果主進程PID存在情況下,向該進程發送信號0,實際上并沒有發送任何信息,只是檢測該進程(或進程組)是否存活,同時也檢測當前用戶是否有權限發送系統信號;
2、為什么主進程PID會保存?系統啟動后脫離當前terminal運行,如果要執行關閉或者其他命令,此時是以另外的一個進程執行該命令,如果我們連進程PID都不知道,那該向誰發信號呢?
所以主進程PID必須保存起來,而且主進程負責監聽其他子進程,所以它是我們繼續操作的入口。
Worker::runAll()
php的socket編程其實和C差不多,后者對socket進行了再包裹,并提供接口給php,在php下網絡編程步驟大大減少。
譬如:stream_socket_server?和?stream_socket_client?直接創建了server/client socke(php有兩套socket操作函數)。wm則大量使用了前者,啟動過程如下(注釋已經非常詳盡):
public?static?function?runAll() ????{ ????????//?初始化環境變量 ????????self::init(); ????????//?解析命令 ????????self::parseCommand(); ????????//?嘗試以守護進程模式運行 ????????self::daemonize(); ????????//?初始化所有worker實例,主要是監聽端口 ????????self::initWorkers(); ????????//??初始化所有信號處理函數 ????????self::installSignal(); ????????//?保存主進程pid ????????self::saveMasterPid(); ????????//?創建子進程(worker進程)并運行 ????????self::forkWorkers(); ????????//?展示啟動界面 ????????self::displayUI(); ????????//?嘗試重定向標準輸入輸出 ????????self::resetStd(); ????????//?監控所有子進程(worker進程) ????????self::monitorWorkers(); ????}
下面還是只說該過程的關鍵點:
1、始化環境變量,例如設置主進程名稱、日志路徑,初始化定時器等等;
2、解析命令行參數,主要用到 $argc 和 $argc 用法同c語言;
3、生成守護進程,以脫離當前終端(兩年前大部分認為PHP無法做daemon,其實這是個誤區!其實PHP在linux的進程模型很穩定,現在wm在商業的應用已經非常成熟,國內某公司每天處理幾億的連接,用于訂單、支付調用,大家可以打消顧慮了);
4、初始化所有worker實例(注意,這里是在主進程做的,只是生成了一堆 server 并沒有設置監聽,多進程模型是在子進程做的監聽,即IO復用);
5、為主進程注冊信號處理函數;
6、保存主進程PID,當系統運行后,我們在終端查看系統狀態或者執行關閉、重啟命令,是通過主進程進行通信,所以需要知道主進程PID,我們知道在終端下敲入一個可執行命令,實則是在當前終端下新建一個子進程來執行,所以我們需要得知主進程PID,以向WM主進程發送SIGNAL,這時信號處理函數捕獲該信號,并通過回調方式執行。
7、創建子進程,設置當前進程用戶(root)。在多進程模型中,兩類子進程,分別監聽不同的server地址,我們在主進程只是創建server并沒有設置監聽,也沒有生成指定數目的server。
原因在于,我們在一個進程多次創建同一個 socket,會報錯, worker數目其實就是 socket 數量,也就是該 socket 的子進程數目,子進程繼承了父進程上下文,但是只監聽特定的 socket 事件;
8、在子進程中,將 server socket 注冊監聽事件,用到一個擴展Event,可以實現IO復用,并注冊數據讀取回調,同時也可注冊socket連接事件回調;
9、輸入輸出重定向;
10、主進程監聽子進程狀態,在一個無限循環中調用 pcntl_signal_dispatch() 函數,用于捕獲子進程退出狀態,該函數會一直阻塞,直到有子進程退出時才觸發;
更多workerman相關知識請關注workerman教程欄目。