Workerman學習筆記(二)Worker類


本篇主要對Worker類進行中文注釋方式學習Workerman

 

啟動文件:http_test.php

<?php
use Workerman\Worker;
require_once __DIR__ . '/wk/Autoloader.php';

// 創建一個Worker監聽2345端口,使用http協議通訊
$http_worker = new Worker("http://0.0.0.0:2345");

// 啟動4個進程對外提供服務
$http_worker->count = 4;

// 接收到瀏覽器發送的數據時回復hello world給瀏覽器
$http_worker->onMessage = function($connection, $data)
{
// 向瀏覽器發送hello world
$connection->send('hello world');
};

// 運行worker
Worker::runAll();

Worker類:

初始化 __construct()
/**
     * Construct.
     *
     * @param string $socket_name
     * @param array  $context_option
     */
    public function __construct($socket_name = '', array $context_option = array())
    {
        // Save all worker instances.
        //返回當前對象的hash id
        $this->workerId                    = \spl_object_hash($this);
        //把當前對象加入到$__workers屬性
        static::$_workers[$this->workerId] = $this;
        //設置PID數組,用於區分所在的對象
        static::$_pidMap[$this->workerId]  = array();

        // Get autoload root path.
        /*產生一條回溯跟蹤
        Array
        (
            [0] => Array
                (
                    [file] => /root/test_wk/http_test.php
                    [line] => 5
                    [function] => __construct
                    [class] => Workerman\Worker
                    [object] => Workerman\Worker Object
                        (
                            [id] => 0
                            [name] => none
                            [count] => 1
                            [user] =>
                            [group] =>
                            [reloadable] => 1
                            [reusePort] =>
                            [onWorkerStart] =>
                            [onConnect] =>
                            [onMessage] =>
                            [onClose] =>
                            [onError] =>
                            [onBufferFull] =>
                            [onBufferDrain] =>
                            [onWorkerStop] =>
                            [onWorkerReload] =>
                            [transport] => tcp
                            [connections] => Array
                                (
                                )

                            [protocol] =>
                            [_autoloadRootPath:protected] =>
                            [_pauseAccept:protected] => 1
                            [stopping] =>
                            [_mainSocket:protected] =>
                            [_socketName:protected] =>
                            [_localSocket:protected] =>
                            [_context:protected] =>
                            [workerId] => 0000000016887ec6000000004cc8efd4
                        )

                    [type] => ->
                    [args] => Array
                        (
                            [0] => http://0.0.0.0:2345
                        )

                )

        )
        */
        $backtrace               = \debug_backtrace();
        //設置自動加載根目錄 本機:/root/test_wk
        $this->_autoloadRootPath = \dirname($backtrace[0]['file']);
        Autoloader::setRootPath($this->_autoloadRootPath);

        // Context for socket.
        if ($socket_name) {
            $this->_socketName = $socket_name;
            //設置內核接受緩存器隊列的大小
            if (!isset($context_option['socket']['backlog'])) {
                $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG;
            }
            //創建一個資源流,流的概念還在理解當中,等理解透徹了再補上^_^
            $this->_context = \stream_context_create($context_option);
        }

        // Turn reusePort on.
        //開啟reusePort,開啟的目的是為了避免驚群效應,提升多進程短連接應用的性能
        //不了解的可以參考這篇文章,說的很清楚 https://www.jianshu.com/p/97cc8c52d47a
        if (static::$_OS === \OS_TYPE_LINUX  // if linux
            && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0
            && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS
            && $this->transport !== 'unix') { // if not unix socket

            $this->reusePort = true;
        }
    }
runAll() 初始化worker並運行環境服務
    /**
     * Run all worker instances.
     *
     * @return void
     */
    public static function runAll()
    {
        static::checkSapiEnv();//判斷腳本啟動方式
        static::init();//初始化
        static::lock();//鎖定當前文件
        static::parseCommand();//cli終端命令解析
        static::daemonize();//以守護進程方式運行
        static::initWorkers();//初始化worker
        static::installSignal();//安裝各類信號
        static::saveMasterPid();//將當前進程ID寫入文件
        static::unlock();//釋放當前文件鎖
        static::displayUI();//啟動后終端顯示內容
        static::forkWorkers();//創建當前進程的子進程
        static::resetStd();//重定向輸入和輸出標准
        static::monitorWorkers();//監聽子進程狀態
    }
checkSapiEnv()  判斷腳本啟動方式
protected static function checkSapiEnv()
    {
        // Only for cli.
        if (\PHP_SAPI !== 'cli') {
            exit("Only run in command line mode \n");
        }
        if (\DIRECTORY_SEPARATOR === '\\') {
            self::$_OS = \OS_TYPE_WINDOWS;
        }
    }
init()
protected static function init()
    {
        //設置用戶自定義的函數來處理腳本中出現的錯誤
        \set_error_handler(function($code, $msg, $file, $line){
            Worker::safeEcho("$msg in file $file on line $line\n");
        });

        // Start file.
        /*產生一條回溯跟蹤,打印結果如下
            Array
            (
                [0] => Array
                    (
                        [file] => /root/test_wk/Workerman/Worker.php
                        [line] => 537
                        [function] => init
                        [class] => Workerman\Worker
                        [type] => ::
                        [args] => Array
                            (
                            )

                    )
                [1] => Array
                    (
                        [file] => /root/test_wk/http_test.php
                        [line] => 14
                        [function] => runAll
                        [class] => Workerman\Worker
                        [type] => ::
                        [args] => Array
                            (
                            )

                    )
            )
        */
        $backtrace        = \debug_backtrace();
        //獲取啟動文件的絕對路徑 本機:/root/test_wk/http_test.php
        static::$_startFile = $backtrace[\count($backtrace) - 1]['file'];

        //啟動文件/替換為_ 本機:_root_test_wk_http_test.php
        $unique_prefix = \str_replace('/', '_', static::$_startFile);

        // Pid file.
        //設置PID路徑,本機:/root/test_wk/Workerman/../_root_test_wk_http_test.php.pid
        if (empty(static::$pidFile)) {
            static::$pidFile = __DIR__ . "/../$unique_prefix.pid";
        }

        // Log file.
        //設置log路徑並創建文件 本機:/root/test_wk/Workerman/../workerman.log
        if (empty(static::$logFile)) {
            static::$logFile = __DIR__ . '/../workerman.log';
        }
        $log_file = (string)static::$logFile;
        if (!\is_file($log_file)) {
            \touch($log_file);
            \chmod($log_file, 0622);
        }

        // State.
        //設置默認狀態值
        static::$_status = static::STATUS_STARTING;

        // For statistics.
        //獲取服務啟動時間
        static::$_globalStatistics['start_timestamp'] = \time();
        //設置啟動狀態的文件 本機:/tmp/_root_test_wk_http_test.php.status
        static::$_statisticsFile                      = \sys_get_temp_dir() . "/$unique_prefix.status";

        // Process title.
        //設置進程名稱 本機:WorkerMan: master process  start_file=/root/test_wk/http_test.php
        static::setProcessTitle(static::$processTitle . ': master process  start_file=' . static::$_startFile);

        // Init data for worker id.
        static::initId();

        // Timer init.
        Timer::init();
    }
setProcessTitle
protected static function setProcessTitle($title)
    {
        //設置用戶自定義的函數來處理腳本中出現的錯誤
        \set_error_handler(function(){});
        // >=php 5.5
        if (\function_exists('cli_set_process_title')) {
            //在cli模式下設置進程名稱
            \cli_set_process_title($title);
        } // Need proctitle when php<=5.5 .
        elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) {
            \setproctitle($title);
        }
        \restore_error_handler();
    }
initId() 初始化進程數量
protected static function initId()
    {
        /*
        初始化進程數量
        Array
        (
            [0] => 0
            [1] => 0
            [2] => 0
            [3] => 0
        )
        */
        foreach (static::$_workers as $worker_id => $worker) {
            $new_id_map = array();
            $worker->count = $worker->count < 1 ? 1 : $worker->count;
            for($key = 0; $key < $worker->count; $key++) {
                $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0;
            }
            static::$_idMap[$worker_id] = $new_id_map;
        }
    }
lock();//鎖定當前文件
protected static function lock()
    {
        //用讀方式打開當前啟動文件
        $fd = \fopen(static::$_startFile, 'r');
        //鎖定當前文件
        if ($fd && !flock($fd, LOCK_EX)) {
            static::log('Workerman['.static::$_startFile.'] already running.');
            exit;
        }
    }
parseCommand();//cli終端命令解析
protected static function parseCommand()
    {
        //判斷當前系統是否是linux,不是的話退出
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        //獲取命令行參數
        global $argv;
        // Check argv;
        $start_file = $argv[0];//執行文件 本機:http_test.php
        $available_commands = array(
            'start',
            'stop',
            'restart',
            'reload',
            'status',
            'connections',
        );
        $usage = "Usage: php yourfile <command> [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n";
        //驗證執行文件后跟的命令參數是否正確
        if (!isset($argv[1]) || !\in_array($argv[1], $available_commands)) {
            if (isset($argv[1])) {
                static::safeEcho('Unknown command: ' . $argv[1] . "\n");
            }
            exit($usage);
        }

        // Get command.
        $command  = \trim($argv[1]);
        $command2 = isset($argv[2]) ? $argv[2] : '';

        // Start command.
        //如果是守護進程方式啟動,寫入日志
        $mode = '';
        if ($command === 'start') {
            if ($command2 === '-d' || static::$daemonize) {
                $mode = 'in DAEMON mode';
            } else {
                $mode = 'in DEBUG mode';
            }
        }
        static::log("Workerman[$start_file] $command $mode");

        // Get master process PID.
        //獲取當前主進程,第一次啟動時獲取不到
        $master_pid      = \is_file(static::$pidFile) ? \file_get_contents(static::$pidFile) : 0;
        //posix_kill($master_pid, 0)是檢查進程是否存在
        //當前進程和主進程進行比對,如果一致則不能重新start,如果不一致只能使用start和restart命令
        $master_is_alive = $master_pid && \posix_kill($master_pid, 0) && \posix_getpid() !== $master_pid;
        // Master is still alive?
        if ($master_is_alive) {
            if ($command === 'start') {
                static::log("Workerman[$start_file] already running");
                exit;
            }
        } elseif ($command !== 'start' && $command !== 'restart') {
            static::log("Workerman[$start_file] not run");
            exit;
        }

        // execute command.
        switch ($command) {
            case 'start':
                if ($command2 === '-d') {    //設置要使用守護進程啟動
                    static::$daemonize = true;
                }
                break;
            case 'status':
                while (1) {
                    if (\is_file(static::$_statisticsFile)) {
                        @\unlink(static::$_statisticsFile);
                    }
                    // Master process will send SIGUSR2 signal to all child processes.
                    \posix_kill($master_pid, SIGUSR2);
                    // Sleep 1 second.
                    \sleep(1);
                    // Clear terminal.
                    if ($command2 === '-d') {
                        static::safeEcho("\33[H\33[2J\33(B\33[m", true);
                    }
                    // Echo status data.
                    static::safeEcho(static::formatStatusData());
                    if ($command2 !== '-d') {
                        exit(0);
                    }
                    static::safeEcho("\nPress Ctrl+C to quit.\n\n");
                }
                exit(0);
            case 'connections':
                if (\is_file(static::$_statisticsFile) && \is_writable(static::$_statisticsFile)) {
                    \unlink(static::$_statisticsFile);
                }
                // Master process will send SIGIO signal to all child processes.
                \posix_kill($master_pid, SIGIO);
                // Waiting amoment.
                \usleep(500000);
                // Display statisitcs data from a disk file.
                if(\is_readable(static::$_statisticsFile)) {
                    \readfile(static::$_statisticsFile);
                }
                exit(0);
            case 'restart':
            case 'stop':
                if ($command2 === '-g') {
                    static::$_gracefulStop = true;
                    $sig = \SIGTERM;
                    static::log("Workerman[$start_file] is gracefully stopping ...");
                } else {
                    static::$_gracefulStop = false;
                    $sig = \SIGINT;
                    static::log("Workerman[$start_file] is stopping ...");
                }
                // Send stop signal to master process.
                $master_pid && \posix_kill($master_pid, $sig);
                // Timeout.
                $timeout    = 5;
                $start_time = \time();
                // Check master process is still alive?
                while (1) {
                    $master_is_alive = $master_pid && \posix_kill($master_pid, 0);
                    if ($master_is_alive) {
                        // Timeout?
                        if (!static::$_gracefulStop && \time() - $start_time >= $timeout) {
                            static::log("Workerman[$start_file] stop fail");
                            exit;
                        }
                        // Waiting amoment.
                        \usleep(10000);
                        continue;
                    }
                    // Stop success.
                    static::log("Workerman[$start_file] stop success");
                    if ($command === 'stop') {
                        exit(0);
                    }
                    if ($command2 === '-d') {
                        static::$daemonize = true;
                    }
                    break;
                }
                break;
            case 'reload':
                if($command2 === '-g'){
                    $sig = \SIGQUIT;
                }else{
                    $sig = \SIGUSR1;
                }
                \posix_kill($master_pid, $sig);
                exit;
            default :
                if (isset($command)) {
                    static::safeEcho('Unknown command: ' . $command . "\n");
                }
                exit($usage);
        }
    }
daemonize();//以守護進程方式運行
protected static function daemonize()
    {
        //如果設置不是守護進程或不是linux系統,則退出
        if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        //umask的作用是在創建新文件或目錄時 屏蔽掉新文件或目錄不應有的訪問允許權限
        //umask(0)是將默認權限掩碼修改為0,意味着即將要創建的文件的權限都是777
        \umask(0);
        //創建一個子進程
        $pid = \pcntl_fork();
        //-1代表創建失敗
        if (-1 === $pid) {
            throw new Exception('Fork fail');
        //子進程創建成功,當前進程退出
        //假如當前進程是1,子進程是2000,那么退出的是當前進程:1
        } elseif ($pid > 0) {
            exit(0);
        }
        //以下內容是從其他地方摘抄
        //創建一個會話領導者,徹底脫離控制終端,當前進程為會話首領進程  
        /**
        1、此進程【不可以是組長進程】將是新會話期的會話期首進程【session leader】,會話期首進程是創建該會話期的進程,此進程是新會話期的唯一進程。
        2、此進程將是新進程組的組長進程,新進程組的id是此調用進程的進程id. 
        3、此進程沒有控制終端。  在調用setsid之前即使有控制終端,調用后它們的聯系將解除
        4、一個會話是多個進程組的集合,並且只能有一個前台進程組。  
        一般來說先調用fork創建兩個進程,讓父進程退出,子進程來創建會話,因為子進程是繼承了父進程的進程組id,其進程是新分配的,不會等於進程組id,保證了創建會話的不是進程組組長進程。
        **/
        if (-1 === \posix_setsid()) {
            throw new Exception("Setsid fail");
        }
        // Fork again avoid SVR4 system regain the control of terminal.
        //此進程已經脫離了原來的會話終端,不在繼承原來的會話了
        //再次創建一個子進程為孫子進程
        //該孫子進程負責繼續運行后面的代碼
        $pid = \pcntl_fork();
        if (-1 === $pid) {
            throw new Exception("Fork fail");
        } elseif (0 !== $pid) {
            exit(0);//非孫子進程全部退出
        }
    }
initWorkers();//初始化worker
protected static function initWorkers()
    {
        //當前非linux系統退出
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        foreach (static::$_workers as $worker) {
            // Worker name.
            //設置worker實例的名字
            if (empty($worker->name)) {
                $worker->name = 'none';
            }

            // Get unix user of the worker process.
            //獲取當前用戶名
            if (empty($worker->user)) {
                $worker->user = static::getCurrentUser();
            } else {
                if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) {
                    static::log('Warning: You must have the root privileges to change uid and gid.');
                }
            }

            // Socket name.
            $worker->socket = $worker->getSocketName();

            // Status name.
            $worker->status = '<g> [OK] </g>';

            // Get column mapping for UI
            foreach(static::getUiColumns() as $column_name => $prop){
                !isset($worker->{$prop}) && $worker->{$prop} = 'NNNN';
                $prop_length = \strlen($worker->{$prop});
                $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength';
                static::$$key = \max(static::$$key, $prop_length);
            }

            // Listen.
            //reusePort屬性已開啟,不在當前進程統一監聽,而是到各個子進程下進行監聽
            if (!$worker->reusePort) {
                $worker->listen();
            }
        }
    }
protected static function getCurrentUser()
    {
        $user_info = \posix_getpwuid(\posix_getuid());
        return $user_info['name'];
    }
installSignal();//安裝各類信號
 protected static function installSignal()
    {
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        $signalHandler = '\Workerman\Worker::signalHandler';
        // stop
        \pcntl_signal(\SIGINT, $signalHandler, false);//前台進程結束,比如ctrl+c
        // graceful stop
        \pcntl_signal(\SIGTERM, $signalHandler, false);//停止進程
        // reload
        \pcntl_signal(\SIGUSR1, $signalHandler, false);//
        // graceful reload
        \pcntl_signal(\SIGQUIT, $signalHandler, false);
        // status
        \pcntl_signal(\SIGUSR2, $signalHandler, false);
        // connection status
        \pcntl_signal(\SIGIO, $signalHandler, false);
        // ignore
        \pcntl_signal(\SIGPIPE, \SIG_IGN, false);
    }
saveMasterPid();//將當前進程ID寫入文件
protected static function saveMasterPid()
    {
        //判斷終端系統
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        //獲取當前進程ID
        static::$_masterPid = \posix_getpid();
        //將進程ID寫入文件
        if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) {
            throw new Exception('can not save pid to ' . static::$pidFile);
        }
    }
unlock();//釋放當前文件鎖
protected static function unlock()
    {
        $fd = \fopen(static::$_startFile, 'r');
        $fd && flock($fd, \LOCK_UN);
    }
forkWorkers();//創建當前進程的子進程
protected static function forkWorkersForLinux()
    {

        foreach (static::$_workers as $worker) {
            //設置worker的名字和長度
            if (static::$_status === static::STATUS_STARTING) {
                if (empty($worker->name)) {
                    $worker->name = $worker->getSocketName();
                }
                $worker_name_length = \strlen($worker->name);
                if (static::$_maxWorkerNameLength < $worker_name_length) {
                    static::$_maxWorkerNameLength = $worker_name_length;
                }
            }
            //循環創建子進程
            while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) {
                static::forkOneWorkerForLinux($worker);
            }
        }
    }
protected static function forkOneWorkerForLinux(self $worker)
    {
        // Get available worker id.
        //獲取子進程數組里的pid,沒有創建時都是0
        //static::$_idMap[$worker_id]
        $id = static::getId($worker->workerId, 0);
        if ($id === false) {
            return;
        }
        //創建子進程
        $pid = \pcntl_fork();
        // For master process.
        //創建子進程成功,在父進程中將子進程pid加入到_pidMap和_idMap
        if ($pid > 0) {
            static::$_pidMap[$worker->workerId][$pid] = $pid;
            static::$_idMap[$worker->workerId][$id]   = $pid;
        } // For child processes.
        //創建子進程成功,在子進程中...
        elseif (0 === $pid) {
            \srand();
            \mt_srand();
            //在子進程中創建socket監聽,這是reusePort的優勢
            if ($worker->reusePort) {
                $worker->listen();
            }
            if (static::$_status === static::STATUS_STARTING) {
                static::resetStd();//重定向輸入和輸出標准
            }
            static::$_pidMap  = array();
            // Remove other listener.
            //刪除其他無用監聽
            foreach(static::$_workers as $key => $one_worker) {
                if ($one_worker->workerId !== $worker->workerId) {
                    $one_worker->unlisten();
                    unset(static::$_workers[$key]);
                }
            }
            //停止鬧鍾信號
            Timer::delAll();
            //設置子進程名稱
            static::setProcessTitle(self::$processTitle . ': worker process  ' . $worker->name . ' ' . $worker->getSocketName());
            //設置當前進程的UID和GID
            $worker->setUserAndGroup();
            $worker->id = $id;
            //停止運行,成為僵屍進程,否則會在while循環中一直運行
            //參考https://www.iminho.me/wiki/blog-15.html
            $worker->run();
            if (strpos(static::$eventLoopClass, 'Workerman\Events\Swoole') !== false) {
                exit(0);
            }
            $err = new Exception('event-loop exited');
            static::log($err);
            exit(250);
        } else {
            throw new Exception("forkOneWorker fail");
        }
    }
protected static function getId($worker_id, $pid)
    {
        return \array_search($pid, static::$_idMap[$worker_id]);
    }
listen()
public function listen()
    {
        if (!$this->_socketName) {
            return;
        }

        // Autoload.
        Autoloader::setRootPath($this->_autoloadRootPath);

        if (!$this->_mainSocket) {
            //獲取socket和address
            $local_socket = $this->parseSocketAddress();

            // Flag.
            $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN;
            $errno = 0;
            $errmsg = '';
            // SO_REUSEPORT.
            if ($this->reusePort) {
                //設置流端口復用
                \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1);
            }

            // Create an Internet or Unix domain server socket.
            //創建一個Internet或Unix域服務器套接字
            //使用stream_socket_accept()開始接受連接,這個在后面
            $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context);
            if (!$this->_mainSocket) {
                throw new Exception($errmsg);
            }
            //暫時忽略設置ssl
            if ($this->transport === 'ssl') {
                \stream_socket_enable_crypto($this->_mainSocket, false);
            } elseif ($this->transport === 'unix') {
                $socket_file = \substr($local_socket, 7);
                if ($this->user) {
                    \chown($socket_file, $this->user);
                }
                if ($this->group) {
                    \chgrp($socket_file, $this->group);
                }
            }

            // Try to open keepalive for tcp and disable Nagle algorithm.
            if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') {
                \set_error_handler(function(){});
                //是tcp傳輸層協議的話導入創建好的流構建socket
                $socket = \socket_import_stream($this->_mainSocket);
                //設置tcp選項,選項還可以通過linux進行修改配置
                //心跳設置
                \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1);
                \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1);
                \restore_error_handler();
            }

            // Non blocking.
            //設置為非阻塞模式
            \stream_set_blocking($this->_mainSocket, false);
        }

        $this->resumeAccept();
    }
protected function parseSocketAddress() {
        if (!$this->_socketName) {
            return;
        }
        // Get the application layer communication protocol and listening address.
        list($scheme, $address) = \explode(':', $this->_socketName, 2);
        // Check application layer protocol class.
        //設置協議類
        if (!isset(static::$_builtinTransports[$scheme])) {
            $scheme         = \ucfirst($scheme);
            $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : 'Protocols\\' . $scheme;
            if (!\class_exists($this->protocol)) {
                $this->protocol = "Workerman\\Protocols\\$scheme";
                if (!\class_exists($this->protocol)) {
                    throw new Exception("class \\Protocols\\$scheme not exist");
                }
            }

            if (!isset(static::$_builtinTransports[$this->transport])) {
                throw new Exception('Bad worker->transport ' . \var_export($this->transport, true));
            }
        } else {
            $this->transport = $scheme;
        }
        //local socket
        //返回socket和address
        return static::$_builtinTransports[$this->transport] . ":" . $address;
    }
public function setUserAndGroup()
    {
        // Get uid.
        /*
        根據用戶名獲取用戶信息
        當前用戶名是root
        array(7) {
          ["name"]=>
          string(4) "root"
          ["passwd"]=>
          string(1) "x"
          ["uid"]=>
          int(0)
          ["gid"]=>
          int(0)
          ["gecos"]=>
          string(4) "root"
          ["dir"]=>
          string(5) "/root"
          ["shell"]=>
          string(9) "/bin/bash"
        }
        */
        $user_info = \posix_getpwnam($this->user);
        if (!$user_info) {
            static::log("Warning: User {$this->user} not exsits");
            return;
        }
        $uid = $user_info['uid'];
        // Get gid.
        //獲取用戶組ID
        if ($this->group) {
            $group_info = \posix_getgrnam($this->group);
            if (!$group_info) {
                static::log("Warning: Group {$this->group} not exsits");
                return;
            }
            $gid = $group_info['gid'];
        } else {
            $gid = $user_info['gid'];
        }

        // Set uid and gid.
        //設置當前進程的組ID和UID,並將用戶名加入到組訪問列表中
        if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) {
            if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) {
                static::log("Warning: change gid or uid fail.");
            }
        }
    }
protected static function monitorWorkersForLinux()
    {
        static::$_status = static::STATUS_RUNNING;
        while (1) {
            // Calls signal handlers for pending signals.
            //調用等待信號的處理器
            //檢查是否有未處理的信號
            \pcntl_signal_dispatch();
            // Suspends execution of the current process until a child has exited, or until a signal is delivered
            $status = 0;
            //等待或返回fork的子進程狀態
            $pid    = \pcntl_wait($status, \WUNTRACED);
            // Calls signal handlers for pending signals again.
            \pcntl_signal_dispatch();
            // If a child has already exited.
            if ($pid > 0) {
                // Find out which worker process exited.
                foreach (static::$_pidMap as $worker_id => $worker_pid_array) {
                    if (isset($worker_pid_array[$pid])) {
                        $worker = static::$_workers[$worker_id];
                        // Exit status.
                        if ($status !== 0) {
                            static::log("worker[" . $worker->name . ":$pid] exit with status $status");
                        }

                        // For Statistics.
                        if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) {
                            static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0;
                        }
                        ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status];

                        // Clear process data.
                        unset(static::$_pidMap[$worker_id][$pid]);

                        // Mark id is available.
                        $id                              = static::getId($worker_id, $pid);
                        static::$_idMap[$worker_id][$id] = 0;

                        break;
                    }
                }
                // Is still running state then fork a new worker process.
                if (static::$_status !== static::STATUS_SHUTDOWN) {
                    static::forkWorkers();
                    // If reloading continue.
                    if (isset(static::$_pidsToRestart[$pid])) {
                        unset(static::$_pidsToRestart[$pid]);
                        static::reload();
                    }
                }
            }

            // If shutdown state and all child processes exited then master process exit.
            if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) {
                static::exitAndClearAll();
            }
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM