处理逻辑
Reactor 处理逻辑
在 Reactor 模型中,一般具备以下组件:
- Synchronous Event Demultiplexer:同步事件分离器,用于监听各种事件,调用方调用监听方法的时候会被阻塞,直到有事件发生,才会返回。对于 Linux 来说,同步事件分离器指的就是 IO 多路复用模型,比如 epoll,poll 等, 对于 Java NIO 来说, 同步事件分离器对应的组件就是 selector,对应的阻塞方法就是 select。
- Handler:本质上是文件描述符,是一个抽象的概念,可以简单的理解为一个一个事件,该事件可以来自于外部,比如客户端连接事件,客户端的写事件等等,也可以是内部的事件,比如操作系统产生的定时器事件等等。
- Event Handler:事件处理器,本质上是回调方法,当有事件发生后,框架会根据 Handler 调用对应的回调方法,在大多数情况下,是虚函数,需要用户自己实现接口,实现具体的方法。
- Concrete Event Handler: 具体的事件处理器,是 Event Handler 的具体实现。
- Initiation Dispatcher:初始分发器,实际上就是 Reactor 角色,提供了一系列方法,对 Event Handler 进行注册和移除;还会调用 Synchronous Event Demultiplexer 监听各种事件;当有事件发生后,还要调用对应的 Event Handler。
Reactor 模型的基本的处理逻辑为:
- 我们注册 Concrete Event Handler 到 Initiation Dispatcher 中。
- Initiation Dispatcher 调用每个 Event Handler 的 get_handle 接口获取其绑定的 Handle。
- Initiation Dispatcher 调用 handle_events 开始事件处理循环。在这里,Initiation Dispatcher 会将步骤 2 获取的所有 Handle 都收集起来,使用 Synchronous Event Demultiplexer 来等待这些 Handle 的事件发生。
- 当某个(或某几个)Handle 的事件发生时,Synchronous Event Demultiplexer 通知 Initiation Dispatcher。
- Initiation Dispatcher 根据发生事件的 Handle 找出所对应的 Handler。
- Initiation Dispatcher 调用 Handler 的 handle_event 方法处理事件。
抽象来说,Reactor 有 4 个核心的操作:
- add:添加 socket 监听到 reactor,可以是 listen socket 也可以使客户端 socket,也可以是管道、eventfd、信号等
- set:修改事件监听,可以设置监听的类型,如可读、可写。可读很好理解,对于 listen socket 就是有新客户端连接到来了需要 accept。对于客户端连接就是收到数据,需要 recv。可写事件比较难理解一些。一个 SOCKET 是有缓存区的,如果要向客户端连接发送 2M 的数据,一次性是发不出去的,操作系统默认 TCP 缓存区只有 256K。一次性只能发 256K,缓存区满了之后 send 就会返回 EAGAIN 错误。这时候就要监听可写事件,在纯异步的编程中,必须去监听可写才能保证 send 操作是完全非阻塞的。
- del:从 reactor 中移除,不再监听事件
- callback:就是事件发生后对应的处理逻辑,一般在 add/set 时制定。C 语言用函数指针实现,JS 可以用匿名函数,PHP 可以用匿名函数、对象方法数组、字符串函数名。
Reactor 只是一个事件发生器,实际对 socket 句柄的操作,如 connect/accept、send/recv、close 是在 callback 中完成的。具体编码可参考下面的 Swoole 伪代码:
Reactor 模型还可以与多进程、多线程结合起来用,既实现异步非阻塞 IO,又利用到多核。目前流行的异步服务器程序都是这样的方式:如
- Nginx:多进程 Reactor
- Nginx+Lua:多进程 Reactor+协程
- Golang:单线程 Reactor+多线程协程
- Swoole:多线程 Reactor+多进程 Worker
协程从底层技术角度看实际上还是异步 IO Reactor 模型,应用层自行实现了任务调度,借助 Reactor 切换各个当前执行的用户态线程,但用户代码中完全感知不到 Reactor 的存在。