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();
            }
        }
    }

 

posted @ 2020-08-06 14:47  事儿爸_董  阅读(1165)  评论(0编辑  收藏  举报