首頁  >  文章  >  php框架  >  workerman源碼分析之啟動過程詳解

workerman源碼分析之啟動過程詳解

尚
轉載
2019-11-25 13:57:133695瀏覽

下面由workerman教學專欄跟大家介紹workerman原始碼分析之啟動過程,希望對需要的朋友有幫助!

workerman源碼分析之啟動過程詳解

#workerman

版本:3.1.8(linux)

模型:GatewayWorker(Worker模型可與之類比)

註:只貼出講解部分程式碼,出處以檔名形式給出,大家可自行查看

workerman最初只開發了Linux版本,win是後來增加的,基於命令列模式運行(cli)。

多進程模型

工作進程,Master、Gateway和Worker,Gateway主要用於處理IO事件,保存客戶端連結狀態,將資料處理請求發送給Worker等工作,Worker則是完全的業務邏輯處理,前者為IO密集型,後者為計算密集型,它們之間通過網絡通信,Gateway和Worker兩兩間註冊通信地址,所以非常方便的進行分散式部署,如果業務處理量大可以單純的增加Worker服務。

workerman源碼分析之啟動過程詳解

它們有一個負責監聽的父進程(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教學欄位。

以上是workerman源碼分析之啟動過程詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除