上一篇文章 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)的更多相关文章

  1. workerman-todpole 执行流程(1)

    该系列文章主要是彻底扒一下 workerman todpole 游戏的实现原理. 提前打个预防针: 由于 Worker 类的静态属性和子类对象的混用(当前类的静态属性存放当前类对象,静态方法循环静态属 ...

  2. 步步深入:MySQL架构总览->查询执行流程->SQL解析顺序

    前言: 一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一篇博文了. 本文将从MySQL总体架构--->查询执行流程--->语句执行顺序来 ...

  3. 第二天 ci执行流程

    第二天 ci执行流程 welcome 页面 this this->load 单入口框架index.php 两个文件夹 system application定义 定义常亮路径 载入 codeign ...

  4. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  5. [Java编程思想-学习笔记]第4章 控制执行流程

    4.1  return 关键字return有两方面的用途:一方面指定一个方法结束时返回一个值:一方面强行在return位置结束整个方法,如下所示: char test(int score) { if ...

  6. ThinkPHP2.2框架执行流程图,ThinkPHP控制器的执行流程

    ThinkPHP2.2框架执行原理.流程图在线手册 ThinkPHP控制器的执行流程 对用户的第一次URL访问 http://<serverIp>/My/index.php/Index/s ...

  7. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

    最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, ...

  8. Servlet执行流程和生命周期【慕课网搬】

    Servlet执行流程(GET方式为例) 首先用户客户端浏览器发出Get方式(点击超链接方式)向浏览器发出请求. 服务器接收到客户端点击超链接,接收到GET请求之后,服务器到WEB.xml中<s ...

  9. javascript事件执行流程分析

    我一直想搞清楚事件在DOM中的传播方式,今天经高人指点终于明白一二.首先扒了一张图: 事件捕获过程:当我们点击TEXT时,首先是window->document->body->div ...

随机推荐

  1. elasticsearch 口水篇(9)Facet

    FACET 1)Terms Facet { "query" : { "match_all" : { } }, "facets" : { &q ...

  2. Ubuntu16.04 创建桌面快捷方式

    一.基本概念 Linux 系统中的Desktop Entry 文件以desktop为后缀名.Desktop Entry 文件是 Linux 桌面系统中用于描述程序启动配置信息的文件. 进入/usr/s ...

  3. 【Mysql】mysql使用触发器创建hash索引

    概述 若设计的数据表中,包含较长的字段,比如URL(通常都会比较长),查询时需要根据该字段进行过滤: select * from table_xxx where url = 'xxxxxxx'; 为了 ...

  4. Flume的基本概念

    Flume 概念 Flume 最早是Cludera提供的日志收集系统,后贡献给Apache.所以目前是Apache下的项目,Flume支持在日志系统中指定各类数据发送方,用于收集数据. Flume 是 ...

  5. MyBatis 对数据库进行CRUD操作

    1.update修改 uodate修改也可以使用之前的机制在配置文件中直接编写sql 但是update语句的set字句中是根据传入的值决定的, 此时可以通过Mybatis提供的标签实现判断动态拼接up ...

  6. 学习笔记之Nearest-Neighbour Searching with PostGIS

    PostgreSQL: Documentation: 10: 7.8. WITH Queries (Common Table Expressions) https://www.postgresql.o ...

  7. 理一下docker在各平台上的运行机制

    理一下docker在各平台上的运行机制 首先,从内核共享与否 docker在linux上共享内核,无需虚拟化,完全支持native功能(https://docs.docker.com/engine/i ...

  8. 去掉user agent stylesheet 浏览器默认样式 [ 2.0 版本 ]

    今天在写一个网页的时候发现一个问题,我的table的样式很奇怪,也没有设置什么样式,跟其他的页面不一样,打开开发者工具一看,发现有这么点样式: 其中右上角:user agent stylesheet ...

  9. 基于JMX动态配置Log4J日志级别

    先来看比较low的修改日志级别的方式,在写程序里面. http://blog.gssxgss.me/java%E8%BF%90%E8%A1%8C%E6%97%B6%E5%8A%A8%E6%80%81% ...

  10. Centos 7 下, 安装odoo 10

    1. Centos在虚拟机中, 最小化安装, 网络连接选择的是 桥接模式, 安装完成后, 是不能直接上网的, 输入root 和密码, 登录进去, 然后执行: [root@localhost ~]# v ...