/首页
/开源
/关于
PHP网络编程-进程控制篇(六)
发表@2019-12-16 09:58:00
更新@2023-03-27 16:58:36
各位佬们、腿子们好,我是老李。今天我就少讲段子和番外,以最快速度直捣黄龙了。 在经过了一个如沐春风、令人神清气爽而又愉悦的工作周后(具体发生了什么你们心里应该有数),总算可以回到以往周六日的节奏了。实际上对于我来说,没有严格意义上的周六日,一直在做事情,只不过所做事情的贡献对象不同而已。WM我已经叨叨了五个章节了,今天我想聊聊关于Workerman进程管理部分的相关源码,如果前五个章节你们都已经仔细研究过了,那么现在阅读Workerman进程管理部分的源码应该会是易如反掌了。 但是按照我一贯的秉性,光阅读分析一下人家的肯定是不够的,事后肯定要自己动手写一下的。不过我这个人吧,有个很大的缺陷,就是非常不擅长[ 写关于分析别人源码的文章 ]。源码分析实际上最适合自己在有了基础后找个安静的时刻配合上一个大屏幕,自己去静静地品,一会儿就有[ 内味儿了 ]。 所以,今天大概就两个任务咯: - 感受一下Workerman进程管理相关代码 - 自己完成山寨WM进程管理相关的代码 请你准备好Workerman最新master分支,Let's ROCK~ 先了解下Workerman的进程模型,这是一种单Master多Worker的进程模型,在这其中: - Master进程fork出固定数量的Worker进程,并在服务运行后负责监控Worker进程的状态,比如Worker挂了后再重新拉起一个,又或者收到信号后reload全部Worker进程 - Worker进程的职责就相对进程,每个Worker进程持有一个event-loop,负责监听各种网络事件,只不过event-loop内容并非本篇所覆盖的范围了 这里的工作流程就是一初始化配置,二daemonize化,三Master进程fork出固定数量的Worker进程,四给Master进程以及Worker进程安装信号处理器。好了,我们就根据这四点对代码进行重点感受。 先从入口函数看起,我们就重点关注与进程相关的几个咯,我分别在上面加了注释: ```php public static function runAll() { static::checkSapiEnv(); static::init(); static::lock(); // 对start、stop、restart、reload动作的解析 // 这个函数我就不单独解析了,比较简单 static::parseCommand(); // 这个没什么好说的,就是daemon化 static::daemonize(); static::initWorkers(); // 安装信号 static::installSignal(); static::saveMasterPid(); static::unlock(); static::displayUI(); // fork出配置数量的Worker进程 static::forkWorkers(); static::resetStd(); // 监控Worker进程 static::monitorWorkers(); } ``` 好了,我们看下static::daemonize()函数,在前面章节里我们是单独解析过daemon进程的底层原理以及其实现的,现如今看下WM的,看看是不是觉得已经更加容易接受理解了: ```php protected static function daemonize() { // 如果配置中daemonize为false 或者 操作系统不是Linux // 那么直接返回 if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { return; } // 设置umask掩码,umask与chmod权限息息相关 \umask(0); // fork进程,主进程退出执行,子进程继续执行 // 注意这里的子进程不是指Worker进程 // 尽管是子进程,但他却依然是Master进程 $pid = \pcntl_fork(); if (-1 === $pid) { throw new Exception('Fork fail'); } elseif ($pid > 0) { exit(0); } // 子进程(Master进程)使用posix_setsid()创建新会话和进程组 // 这一句话便足以让当前进程脱离控制终端! if (-1 === \posix_setsid()) { throw new Exception("Setsid fail"); } // 下面这句英文注释是 亮哥 写的,大概意思之前也说过,就是 // 避免SVR4某些情况下情况下进程会再次获得控制终端 // Fork again avoid SVR4 system regain the control of terminal. $pid = \pcntl_fork(); // 主进程再次终止运行,最终的子进程会成为Master进程变成 // daemon程序运行在后台,然后继续fork出Worker进程 if (-1 === $pid) { throw new Exception("Fork fail"); } elseif (0 !== $pid) { exit(0); } } ``` 然后是安装信号处理,大概是这样的: ```php protected static function installSignal() { // 如果不是Linux就GG吧 if (static::$_OS !== \OS_TYPE_LINUX) { return; } // signalHandler便是具体的信号处理函数 $signalHandler = '\Workerman\Worker::signalHandler'; // stop // 捕获SIGINT信号,实现stop命令 \pcntl_signal(\SIGINT, $signalHandler, false); // graceful stop // 捕获SIGTERM信号,实现柔性停止 \pcntl_signal(\SIGTERM, $signalHandler, false); // reload // 捕获SIGUSR1,实现reload \pcntl_signal(\SIGUSR1, $signalHandler, false); // graceful reload // 捕获SIGQUIT信号,实现柔性加载 \pcntl_signal(\SIGQUIT, $signalHandler, false); // status // 捕获SIGUSR2信号,实现status \pcntl_signal(\SIGUSR2, $signalHandler, false); // connection status // SIGIO信号,不属于本节内容,后面会继续提 \pcntl_signal(\SIGIO, $signalHandler, false); // ignore // 捕获SIGPIPE信号,忽略掉所有管道事件 \pcntl_signal(\SIGPIPE, \SIG_IGN, false); } // 然后我们需要再继续关注下signalHandler函数,这个函数里是对各个 // 信号响应的具体业务方法 public static function signalHandler($signal) { switch ($signal) { // Stop. // 实际上case SIGINT和case SIGTERM的逻辑非常简单, // 唯一区别就是\$_gracefulStop参数 // 所以重点只需要放在stopAll()函数上了 // 这个函数实现了Workerman停止的逻辑 case \SIGINT: static::$_gracefulStop = false; static::stopAll(); break; // Graceful stop. case \SIGTERM: static::$_gracefulStop = true; static::stopAll(); break; // Reload. // 下面也就简单了,重点放在reload()函数上即可 // 这个函数实现了对所有Worker进程reload热加载 // 而在reload函数之前,首先需要使用getAllWorkerPids() // 获取到所有待reload的Worker进程的pid们 case \SIGQUIT: case \SIGUSR1: if($signal === \SIGQUIT){ static::$_gracefulStop = true; }else{ static::$_gracefulStop = false; } static::$_pidsToRestart = static::getAllWorkerPids(); static::reload(); break; // Show status. case \SIGUSR2: static::writeStatisticsToStatusFile(); break; // Show connection status. case \SIGIO: static::writeConnectionsStatisticsToStatusFile(); break; } } ``` ![](https://ti-node.com/static/upload/PNP/img_43.png#width-full) (突发事件插播:TMD,现在是凌晨2点[ 写完的时候已经凌晨五点了 ],我本来睡地刚刚好,突然听到外面好像有人在唱歌,关掉还在播放着的我桃儿正在讲着的关于谦儿哥爸爸大肠刺身的事儿,这才听清楚原来是一男一女在楼下的草地里不知道干啥,女的好像在哭,男的好像在吐,这TM大半夜的,一个哭一个吐,我是被折腾地睡不着了,索性起来写文章了... ...我楼层低,吐的时候那哗啦哗啦地声音好像就跟在我屋里吐似的。这不仅让我想起刚来北京那会儿住在宋家庄贫民窟的日子里,当时我房租300块而且水电费还不用管,房子就是三合板搞定的,自带空调效果,温度随外界变化而变化。住在我隔壁的是一情侣,他们还养了一条狗子。每天晚上老子回来的时候一推门进去,TA们家的那傻狗就以为是有人要进TA们屋了,然后就是一顿顿哇哇狂吠;这还不是最沙雕的,最沙雕的是TA俩办事儿的时候,由于隔音以及隔震动的效果都贼差,我总有一种TA俩就在我床上办事儿的错觉,每当我忍不住笑的时候,TA俩就停了,我估计TA也有我就在TA们床上笑的错觉,然后停个几十秒后就悉悉嗦嗦又开始了...) 好了,让我们重点看stop和reload大概是如何实现的,我们约定顺序为先看stop后看reload: ```php ```