本篇文章技术关键词PHP多进程PHP原生网络函数PHP事件驱动编程PHP信号处理PHP进程脱离中断成为守护进程单进程监听多个端口多进程监听同一端口。
本篇的流程是顺着workerman的代码执行流程走一遍知道其是怎么运行的其他一些疑难问题留在下一篇文章中讨论。
在码农界PHP给大家的印象一直就是渲染网站的一门语言操作socket做网络方面的编程不是很在行但一位大牛利用PHP的操作网络的函数封装出Workerman框架它给PHP程序员编写网络代码极大的方便。它是一款高性能异步PHP socket即时通讯框架在不安装其他扩展的情况下很方便的操作socket编写TCP,UDP等程序。
Workerman官网有作者自己录的讲解核心原理的视频但是观看需要498我最近利用空闲时间研究了下框架的执行流程写下来一起讨论下如果对你有所帮助那可能你就赚了498。哈哈。
我分析的Workerman代码版本是3.5.11下载地址https://github.com/walkor/Workerman/releases/tag/v3.5.11
1、官网实例
use Workerman\Worker;
require_once __DIR__ . '/Workerman/Autoloader.php';
// 注意这里与上个例子不同使用的是websocket协议
$ws_worker = new Worker("websocket://0.0.0.0:2000");
// 启动4个进程对外提供服务
$ws_worker->count = 4;
// 当收到客户端发来的数据后返回hello $data给客户端
$ws_worker->onMessage = function($connection, $data)
{
// 向客户端发送hello $data
$connection->send('hello ' . $data);
};
// 运行worker
Worker::runAll();上面代码命名为ws_test.php在cli命令行模式下使用 php ws_test.php start后程序启动我们也顺着看程序是怎样执行的。这是一个简单的使用websocket协议的例子收到消息前面加上hello把信息返回。
2、首先命名空间载入Autoloader文件文件代码如下
class Autoloader
{
//Autoload 跟目录
protected static $_autoloadRootPath = '';
//设置根目录
public static function setRootPath($root_path)
{
self::$_autoloadRootPath = $root_path;
}
//根据命名空间载入文件
public static function loadByNamespace($name)
{
$class_path = str_replace('/', DIRECTORY_SEPARATOR, $name);
if (strpos($name, 'Workerman/') === 0) {
$class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php';
} else {
if (self::$_autoloadRootPath) {
$class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php';
}
if (empty($class_file) || !is_file($class_file)) {
$class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php";
}
}
if (is_file($class_file)) {
require_once($class_file);
if (class_exists($name, false)) {
return true;
}
}
return false;
}
}
spl_autoload_register('\Workerman\Autoloader::loadByNamespace');
| 这段代码的作用主要是当代码中new的类不存在时php会执行spl_autoload_register传入的函数此函数去引入类文件在new就可以执行了composer引入包也是这个原理。
|
3、官网代码中下一步是
1
| $ws_worker = new Worker("websocket://0.0.0.0:2000"); |
new了worker类这个类是workerman的关键我们来看下worker类的__construct函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public function __construct($socket_name = '', $context_option = array()) { //设置workerId。spl_object_hash()函数获取当前对象唯一的hash值用来标记。 $this->workerId = spl_object_hash($this); //保存主进程的对象 static::$_workers[$this->workerId] = $this; //保存主进程下的子进程的PID static::$_pidMap[$this->workerId] = array();
// 该函数可以获得当前执行的信息当前的函数名当前的文件名等。 $backtrace = debug_backtrace(); //设置自动加载根目录 $this->_autoloadRootPath = dirname($backtrace[0]['file']);
// 设置socket上下文。 //设置_socketName //最后调用straem_context_create() 创建资源流上下文 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); } } |
执行完__construct后继续向下执行段1代码
1 2 3 4 5 6 7 8 9
| $ws_worker->count = 4;
$ws_worker->onMessage = function($connection, $data) { // 向客户端发送hello $data $connection->send('hello ' . $data); };
Worker::runAll(); |
设置worker属性count设置了一个回调函数。
4、最后执行了静态方法runAll()。这个方法是运行起workerman的地方来看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <pre>public static function runAll() { static::checkSapiEnv(); static::init(); static::parseCommand(); static::daemonize(); static::initWorkers(); static::installSignal(); static::saveMasterPid(); static::displayUI(); static::forkWorkers(); static::resetStd(); static::monitorWorkers(); }</pre> |
这么多静态方法运行起来后程序就启动了。下面我们一个一个拆开看。
5、checkSapiEnv() 判断系统是不是cli命令行模式判断系统是否为Windows
1 2 3 4 5 6 7 8 9 10
| <pre>protected static function checkSapiEnv() { // Only for cli. if (php_sapi_name() != "cli") { exit("only run in command line mode \n"); } if (DIRECTORY_SEPARATOR === '\') { self::$_OS = OS_TYPE_WINDOWS; } }</pre> |
6、init() worker环境初始化基本属性初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <pre>protected static function init() { //出错后会触发打印出错信息 set_error_handler(function($code, $msg, $file, $line){ Worker::safeEcho("$msg in file $file on line $line\n"); });
// 上个文件也就是ws_test.php的文件名 $backtrace = debug_backtrace(); static::$_startFile = $backtrace[count($backtrace) - 1]['file']; $unique_prefix = str_replace('/', '_', static::$_startFile);
// Pid 文件地址.存放pid信息 if (empty(static::$pidFile)) { static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; }
// 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); }
// 当前状态 static::$_status = static::STATUS_STARTING;
// For statistics. static::$_globalStatistics['start_timestamp'] = time(); //又新建一个文件用来存放当前进程的网络相关信息 static::$_statisticsFile = sys_get_temp_dir() . "/$unique_prefix.status";
// 设置进程标题 static::setProcessTitle('WorkerMan: master process start_file=' . static::$_startFile);
// 主进程跟子进程的映射如[worker_id=>[0=>$pid, 1=>$pid, ..], ..]跟$_pidMap一样只不过键不一样 //具体可以看文章最后的完整代码 static::initId(); // 时间初始化.详细流程之后讲 Timer::init(); }</pre> |
内网穿透– 用我最直白的语言解读优秀源码。
7、parseCommand() 命令行参数解析解析如php ws_test.php start -d的含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| protected static function parseCommand() { if (static::$_OS !== OS_TYPE_LINUX) { return; } global $argv; // 获取命令行参数; $start_file = $argv[0]; $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); }
// 获取. $command = trim($argv[1]); $command2 = isset($argv[2]) ? $argv[2] : '';
// start命令. $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_is_alive = $master_pid && posix_kill($master_pid, 0) && posix_getpid() != $master_pid; 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); }
// 主进程给所有子进程发送SIGUSR2信号子进程执行此信号的相关函数信息写入一个文件 posix_kill($master_pid, SIGUSR2); // 稍等. sleep(1); // 清理终端记录. if ($command2 === '-d') { static::safeEcho("\33[H\33[2J\33(B\33[m", true); } // 输入. 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); } // 主进程给所有子进程发送SIGIO信号子进程执行此信号的相关函数信息写入一个文件 posix_kill($master_pid, SIGIO); // 稍等. usleep(500000); // 读状态文件并显示 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 ..."); } // 给主进程发sig信号. $master_pid && posix_kill($master_pid, $sig); $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) { // 判断五秒内没有停止则强制 if (!static::$_gracefulStop && time() - $start_time >= $timeout) { static::log("Workerman[$start_file] stop fail"); exit; } // 稍等继续循环. usleep(10000); continue; } // 成功停止 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); } } |
8、daemonize() 主进程是否进入守护模式终端断开程序继续运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| protected static function daemonize() { if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { return; } //创建文件屏蔽码设置0防止父进程影响 umask(0); //pcntl_fork(),fork子进程返回值<0错误=0字进程>0父进程 $pid = pcntl_fork(); if (-1 === $pid) { throw new Exception('fork fail'); } elseif ($pid > 0) { //父进程退出脱离终端 exit(0); } if (-1 === posix_setsid()) { throw new Exception("setsid fail"); } //这里没懂官方讲解如下试了下不用再次fork也成功 // 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); } } |
9、initWorkers() 初始化所有worker实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| protected static function initWorkers() { if (static::$_OS !== OS_TYPE_LINUX) { return; } foreach (static::$_workers as $worker) { // worker名字. if (empty($worker->name)) { $worker->name = 'none'; }
// 获取最长的名字长度 $worker_name_length = strlen($worker->name); if (static::$_maxWorkerNameLength < $worker_name_length) { static::$_maxWorkerNameLength = $worker_name_length; }
//获取最长的 socket name长度. $socket_name_length = strlen($worker->getSocketName()); if (static::$_maxSocketNameLength < $socket_name_length) { static::$_maxSocketNameLength = $socket_name_length; }
// 当前主进程是不是root权限. 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.'); } }
//获取最长的 linux user名字长度 $user_name_length = strlen($worker->user); if (static::$_maxUserNameLength < $user_name_length) { static::$_maxUserNameLength = $user_name_length; }
// 不使用系统级的端口复用的话.在主进程设置好listen信息否则在子进程设置 if (!$worker->reusePort) { //监听看最后的注释 $worker->listen(); } } } |
10、installSignal() 安装主进程信号处理函数stop,reload,status,ignore信号触发就执行worker::signalHanlder()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected static function installSignal() { if (static::$_OS !== OS_TYPE_LINUX) { return; } // stop pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); // graceful stop pcntl_signal(SIGTERM, array('\Workerman\Worker', 'signalHandler'), false); // reload pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false); // graceful reload pcntl_signal(SIGQUIT, array('\Workerman\Worker', 'signalHandler'), false); // status pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false); // connection status pcntl_signal(SIGIO, array('\Workerman\Worker', 'signalHandler'), false); // ignore pcntl_signal(SIGPIPE, SIG_IGN, false); } |
内网穿透– 用我最直白的语言解读优秀源码。
11、saveMasterPid() 保存主进程id到pid文件worker::signalHanlder()
1 2 3 4 5 6 7 8 9 10 11
| protected static function saveMasterPid() { if (static::$_OS !== OS_TYPE_LINUX) { return; } //获取进程pid static::$_masterPid = posix_getpid(); if (false === file_put_contents(static::$pidFile, static::$_masterPid)) { throw new Exception('can not save pid to ' . static::$pidFile); } } |
12、displayUI() 在终端显示运行状态如下图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| protected static function displayUI() { global $argv; if (in_array('-q', $argv)) { return; } if (static::$_OS !== OS_TYPE_LINUX) { static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); static::safeEcho('Workerman version:'. static::VERSION. " PHP version:". PHP_VERSION. "\r\n"); static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); static::safeEcho("worker listen processes status\r\n"); return; } static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); static::safeEcho('Workerman version:'. static::VERSION. " PHP version:". PHP_VERSION. "\r\n"); static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); static::safeEcho("user". str_pad('', static::$_maxUserNameLength + 2 - strlen('user')). "worker". str_pad('', static::$_maxWorkerNameLength + 2 - strlen('worker')). "listen". str_pad('', static::$_maxSocketNameLength + 2 - strlen('listen')). "processes status\n"); foreach (static::$_workers as $worker) { static::safeEcho(str_pad($worker->user, static::$_maxUserNameLength + 2). str_pad($worker->name, static::$_maxWorkerNameLength + 2). str_pad($worker->getSocketName(), static::$_maxSocketNameLength + 2). str_pad(' ' . $worker->count, 9). " [OK] \n"); } static::safeEcho("----------------------------------------------------------------\n"); if (static::$daemonize) { static::safeEcho("Input "php $argv[0] stop" to stop. Start success.\n\n"); } else { static::safeEcho("Press Ctrl+C to stop. Start success.\n"); } } |
13、forkWorkers() 核心代码fork子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| protected static function forkWorkers() { if (static::$_OS === OS_TYPE_LINUX) { static::forkWorkersForLinux(); } else { static::forkWorkersForWindows(); } }
protected static function forkWorkersForLinux() {
foreach (static::$_workers as $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; } } //fork count个子进程 while (count(static::$_pidMap[$worker->workerId]) < $worker->count) { static::forkOneWorkerForLinux($worker); } } }
protected static function forkOneWorkerForLinux($worker) { // 有这个workerid $id = static::getId($worker->workerId, 0); if ($id === false) { return; } //fork子进程 $pid = pcntl_fork(); // 主进程 if ($pid > 0) { static::$_pidMap[$worker->workerId][$pid] = $pid; static::$_idMap[$worker->workerId][$id] = $pid; } // 子进程 elseif (0 === $pid) { //是否使用系统的端口复用使用的话自己执行否则之前主进程已经listen过 if ($worker->reusePort) { $worker->listen(); } if (static::$_status === static::STATUS_STARTING) { //不要再终端输入输出 static::resetStd(); } static::$_pidMap = array(); // 移除其他监听但workers数组只会有一个在我看来不会有其他监听这样写可能是为了预防错误 foreach(static::$_workers as $key => $one_worker) { if ($one_worker->workerId !== $worker->workerId) { $one_worker->unlisten(); unset(static::$_workers[$key]); } } //清除定时任务 Timer::delAll(); static::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); $worker->setUserAndGroup(); $worker->id = $id; $worker->run(); $err = new Exception('event-loop exited'); static::log($err); exit(250); } else { throw new Exception("forkOneWorker fail"); } }
public function run() { //更新状态 static::$_status = static::STATUS_RUNNING;
// 注册一个会在php中止时执行的函数 register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors'));
// 设置根路径 Autoloader::setRootPath($this->_autoloadRootPath);
// 全局事件. if (!static::$globalEvent) { $event_loop_class = static::getEventLoopName(); static::$globalEvent = new $event_loop_class; $this->resumeAccept(); }
// 信号重新安装 static::reinstallSignal();
// 定时初始化 Timer::init(static::$globalEvent);
// 设置 onMessage 函数. if (empty($this->onMessage)) { $this->onMessage = function () {}; }
restore_error_handler(); // T执行 onWorkerStart 回调. if ($this->onWorkerStart) { try { call_user_func($this->onWorkerStart, $this); } catch (\Exception $e) { static::log($e); // Avoid rapid infinite loop exit. sleep(1); exit(250); } catch (\Error $e) { static::log($e); // Avoid rapid infinite loop exit. sleep(1); exit(250); } }
// 事件监听. static::$globalEvent->loop(); }
public function resumeAccept() { // Register a listener to be notified when server socket is ready to read. // 注册socket监听函数当socket可读取时执行也就是读数据函数acceptConnection if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { if ($this->transport !== 'udp') { static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); } else { static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); } $this->_pauseAccept = false; } } |
14、daemonize() 重定向标准输出设置标准输出到指定输出文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static function resetStd() { if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { return; } global $STDOUT, $STDERR; $handle = fopen(static::$stdoutFile, "a"); if ($handle) { unset($handle); set_error_handler(function(){}); fclose($STDOUT); fclose($STDERR); fclose(STDOUT); fclose(STDERR); $STDOUT = fopen(static::$stdoutFile, "a"); $STDERR = fopen(static::$stdoutFile, "a"); // change output stream static::$_outputStream = null; static::outputStream($STDOUT); restore_error_handler(); } else { throw new Exception('can not open stdoutFile ' . static::$stdoutFile); } } |
15、monitorWorkers() 监控worker进程子进程退出再建一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| protected static function monitorWorkers() { if (static::$_OS === OS_TYPE_LINUX) { static::monitorWorkersForLinux(); } else { static::monitorWorkersForWindows(); } }
protected static function monitorWorkersForLinux() { static::$_status = static::STATUS_RUNNING; while (1) { // 来信号后执行下注册函数 pcntl_signal_dispatch(); // pcntl_wait等某个子进程结束或者出错 $status = 0; $pid = pcntl_wait($status, WUNTRACED); // 来信号后执行下注册函数 again. pcntl_signal_dispatch(); //如果推出 if ($pid > 0) { // 找出id并删掉 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]);
// 键改为0可以使用 $id = static::getId($worker_id, $pid); static::$_idMap[$worker_id][$id] = 0;
break; } } // 状态不是关闭在fork一个子进程 if (static::$_status !== static::STATUS_SHUTDOWN) { static::forkWorkers(); // If reloading continue. if (isset(static::$_pidsToRestart[$pid])) { unset(static::$_pidsToRestart[$pid]); static::reload(); } } else { // 关所有子进程跟主进程 if (!static::getAllWorkerPids()) { static::exitAndClearAll(); } } } else { // 关所有子进程跟主进程 if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { static::exitAndClearAll(); } } } } |
至此流程执行结束关于定时类事件触发类下篇文章讲解。最后贴上worker类完整代码其实作者的注释也挺全的。
内网穿透– 用我最直白的语言解读优秀源码。
打开App,阅读手记