workerman-todpole 执行流程(2)
上一篇文章 workerman-todpole 执行流程(1),我们已经分析完了主进程的执行流程,这篇文章主要分析一下子进程的 run() 流程。
有必要提一下,在 run() 开始之前,其实针对角色对象的构造属性 $socket_name 已经开始了连接监听;下面开始分析 run() 方法:
run()
此时的 run() 方法虽然还属于 Worker 类的子类对象,但执行已经在子进程中,看一下代码:
|
public function run()
{
//Update process state.
static::$_status = static::STATUS_RUNNING;
// Register shutdown function for checking errors.
register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors'));
// Set autoload root path.
Autoloader::setRootPath($this->_autoloadRootPath);
// Create a global event loop.
if (!static::$globalEvent) {
$event_loop_class = static::getEventLoopName();
static::$globalEvent = new $event_loop_class;
$this->resumeAccept();
}
// Reinstall signal.
static::reinstallSignal();
// Init Timer.
Timer::init(static::$globalEvent);
// Set an empty onMessage callback.
if (empty($this->onMessage)) {
$this->onMessage = function () {};
}
// Try to emit onWorkerStart callback.
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);
}
}
// Main loop.
static::$globalEvent->loop();
}
|
首先将 Worker::$_status 标记为 STATUS_RUNNING,这段代码之前执行过一次,但那时还位于主进程空间内,所以这里我们可以理解为标记的是子进程的执行状态;紧接着注册错误处理方法来兜底,捕捉子进程出错的原因并记录日志。
getEventLoopName()
关于 Event Loop 这里不展开讲了,将来会单独发几篇文章,接着上面看一下如何创建 $globalEvent 对象,首先是 getEventLoopName() 代码:
|
protected static function getEventLoopName()
{
if (static::$eventLoopClass) {
return static::$eventLoopClass;
}
$loop_name = '';
foreach (static::$_availableEventLoops as $name=>$class) {
if (extension_loaded($name)) {
$loop_name = $name;
break;
}
}
if ($loop_name) {
if (interface_exists('\React\EventLoop\LoopInterface')) {
switch ($loop_name) {
case 'libevent':
static::$eventLoopClass = '\Workerman\Events\React\LibEventLoop';
break;
case 'event':
static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop';
break;
default :
static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop';
break;
}
} else {
static::$eventLoopClass = static::$_availableEventLoops[$loop_name];
}
} else {
static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select';
}
return static::$eventLoopClass;
}
|
整个方法就是为了找到一个合适的 $eventLoopClass(已经有的话直接返回),没有的话则找一个合适的,首先看一下 $_availableEventLoops 有哪些:
|
protected static $_availableEventLoops = array(
'libevent' => '\Workerman\Events\Libevent',
'event' => '\Workerman\Events\Event'
);
|
这两个类包括 Workerman\Events\Ev 和 Workerman\Events\Select 都实现自同一个接口:

除了 Select 之外,每种实现都基于已有的扩展:
libevent 属于老牌实现,libev 是在此基础上的精简和优化版本,由于 libev 不支持 windows,所以在此基础上又有了 libuv(nodejs 的 event-loop 即基于此库)。
回到 getEventLoopName(),首先判断 $_availableEventLoops 对应类的扩展是否安装,优先使用 libevent,如果都没有则使用本地实现 Workerman\Events\Select(死循环)。
除此之外还会判断是否引入了 ReactPHP(接口 \React\EventLoop\LoopInterface 是否存在),存在的话则使用 ReactPHP 相应的封装过的类。
拿到 $eventLoopClass 类之后,创建对象放入 $globalEvent 中,接下来的分析中,我们都以 Ev 实现为例。
reinstallSignal()
该方法将之前安装好的 signal handler 全部 ignore 掉,再通过刚刚创建的 $globalEvent 对象的 add()方法进行注册,和之前的主进程自己 dispatch 不同的是,子进程是通过 signal watcher 来监听信号事件的:
|
public function add($fd, $flag, $func, $args = null)
{
$callback = function ($event, $socket) use ($fd, $func) {
try {
call_user_func($func, $fd);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
};
switch ($flag) {
case self::EV_SIGNAL:
$event = new \EvSignal($fd, $callback);
$this->_eventSignal[$fd] = $event;
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd;
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
$event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
$this->_eventTimer[self::$_timerId] = $event;
return self::$_timerId++;
default :
$fd_key = (int)$fd;
$real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
$event = new \EvIo($fd, $real_flag, $callback);
$this->_allEvents[$fd_key][$flag] = $event;
return true;
}
}
|
这里注册到 EvSignal 的 $callback 还是当初主进程使用的那个 signalHandler(),其实完全可以创建一个新的方法给子进程用的,我们翻代码发现信号处理调用的方法比如 stopAll()/reload() 会判断当前是处于主进程还是子进程,从而执行不同的操作,这也是为什么当初 fork 工作进程时要执行下面的操作:
|
static::$_workers = array($worker->workerId => $worker)
|
这样,主进程存放的是所有角色,而子进程只会存放自己所属的角色。
Timer::init()
这一步是将之前创建的 $globalEvent 对象放到定时器中(定时器每个子进程都有)。
回调
判断当前 onMessage() 回调是否设置,没有设置则默认给一个空的匿名闭包;另外 onWorkerStart() 回调正是在这一步被执行的。
loop()
终于到了最后一步,Ev 实现的 loop() 方法:
|
public function loop()
{
\Ev::run();
}
|
执行后子进程启动完成,默默的在后台等待 event-loop 给它触发。
至此,包括前一篇文章,主进程和子进程的启动流程已经分析完毕;但有个问题,我们仅仅是分析了 Worker 中的默认实现,并没有考虑到其实 BusinessWorker/Gateway/Register 已经重写了部分方法,因此,在接下来的文章中将依次介绍各个实现的执行流程。
workerman-todpole 执行流程(2)的更多相关文章
- workerman-todpole 执行流程(1)
该系列文章主要是彻底扒一下 workerman todpole 游戏的实现原理. 提前打个预防针: 由于 Worker 类的静态属性和子类对象的混用(当前类的静态属性存放当前类对象,静态方法循环静态属 ...
- 步步深入:MySQL架构总览->查询执行流程->SQL解析顺序
前言: 一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一篇博文了. 本文将从MySQL总体架构--->查询执行流程--->语句执行顺序来 ...
- 第二天 ci执行流程
第二天 ci执行流程 welcome 页面 this this->load 单入口框架index.php 两个文件夹 system application定义 定义常亮路径 载入 codeign ...
- 轻量级前端MVVM框架avalon - 执行流程2
接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...
- [Java编程思想-学习笔记]第4章 控制执行流程
4.1 return 关键字return有两方面的用途:一方面指定一个方法结束时返回一个值:一方面强行在return位置结束整个方法,如下所示: char test(int score) { if ...
- ThinkPHP2.2框架执行流程图,ThinkPHP控制器的执行流程
ThinkPHP2.2框架执行原理.流程图在线手册 ThinkPHP控制器的执行流程 对用户的第一次URL访问 http://<serverIp>/My/index.php/Index/s ...
- 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, ...
- Servlet执行流程和生命周期【慕课网搬】
Servlet执行流程(GET方式为例) 首先用户客户端浏览器发出Get方式(点击超链接方式)向浏览器发出请求. 服务器接收到客户端点击超链接,接收到GET请求之后,服务器到WEB.xml中<s ...
- javascript事件执行流程分析
我一直想搞清楚事件在DOM中的传播方式,今天经高人指点终于明白一二.首先扒了一张图: 事件捕获过程:当我们点击TEXT时,首先是window->document->body->div ...
随机推荐
- linux 简单笔记
Linux查看端口使用状态.关闭端口方法 http://blog.csdn.net/wudiyi815/article/details/7473097
- 黄聪:PHP代码获取客户端IP地址经纬度及所在城市
echo $_SERVER['HTTP_HOST']; //echo $_SERVER['REQUEST_URI']; $getIp=$_SERVER["REMOTE_ADDR"] ...
- 黄聪:清理微信浏览网站的缓存,Cookie
微信官方说明是取消关注,但是开发中发现取消关注缓存还是存在! 解决方法如下: 方法一: 用微信内置浏览器打开这个网页debugx5.qq.com ,就会有清除缓存的选项,如下图 方法二: 如果你用An ...
- Eclipse创建一个mybatis工程实现连接数据库查询
Eclipse上创建第一mybatis工程实现数据库查询 步骤: 1.创建一个java工程 2.创建lib文件夹,加入mybatis核心包.依赖包.数据驱动包.并为jar包添加路径 3.创建resou ...
- Zuul Read Time out 错误
只改application.yml文件即可: eureka: client: serviceUrl: defaultZone: http://localhost:7087/eureka/ server ...
- springboot(二 如何访问静态资源和使用模板引擎,以及 全局异常捕获)
在我们开发Web应用的时候,需要引用大量的js.css.图片等静态资源. 默认配置 Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static / ...
- 面试总结之Google
准备Google面试的总结 - 知乎 https://zhuanlan.zhihu.com/p/40866467 [不周山之算法与数据结构]壹 总览 | 小土刀 https://wdxtub.com/ ...
- jQuery的效果
jQuery的效果也是极其强大的 学习方法的三要素 功能 参数 返回值 fadeout() 由可见过渡到隐藏 三个参数 第一个参数:毫秒(过渡的周期) 第二个参数:匀速(过渡的效果) 第三个参数: ...
- HDOJ 2004 成绩转换
#include<cstdio> #include<iostream> using namespace std; int main() { int score; while ( ...
- python常用模块: random模块, time模块, sys模块, os模块, 序列化模块
一. random模块 import random # 任意小数 print(random.random()) # 0到1的任意小数 print(random.uniform(-10, 10)) # ...