libevent之Reactor模式
通过前边的一篇博文轻量级网络库libevent初探,我们知道libevent实际上是封装了不同操作系统下的/dev/poll、kqueue、event ports、select、poll和epoll事件机制,从而给我们提供一个统一的接口。
libevent采用了Reactor I/O 设计模式,而Reactor是基于同步I/O机制的,所以libevent实际是一个基于同步I/O机制的库。
对于I/O设计模式,与Reactor相对应的还有Proactor。下边我们先来看下这两者的不同之处。
Reactor & Proactor
Reactor是基于同步I/O机制,而Proactor则基于异步I/O机制。这是两者最大的区别。
在博文Comparing Two High-Performance I/O Design Patterns中,作者对这两者给出了很精辟的的解释(不翻译了...人家已经说的很清楚):
“
In general, I/O multiplexing mechanisms rely on an event demultiplexor [1, 3], an object that dispatches I/O events from a limited number of sources to the appropriate read/write event handlers. The developer registers interest in specific events and provides event handlers, or callbacks. The event demultiplexor delivers the requested events to the event handlers.
Two patterns that involve event demultiplexors are called Reactor and Proactor [1]. The Reactor patterns involve synchronous I/O, whereas the Proactor pattern involves asynchronous I/O. In Reactor, the event demultiplexor waits for events that indicate when a file descriptor or socket is ready for a read or write operation. The demultiplexor passes this event to the appropriate handler, which is responsible for performing the actual read or write.
In the Proactor pattern, by contrast, the handler—or the event demultiplexor on behalf of the handler—initiates asynchronous read and write operations. The I/O operation itself is performed by the operating system (OS). The parameters passed to the OS include the addresses of user-defined data buffers from which the OS gets data to write, or to which the OS puts data read. The event demultiplexor waits for events that indicate the completion of the I/O operation, and forwards those events to the appropriate handlers. For example, on Windows a handler could initiate async I/O (overlapped in Microsoft terminology) operations, and the event demultiplexor could wait for IOCompletion events [1]. The implementation of this classic asynchronous pattern is based on an asynchronous OS-level API, and we will call this implementation the "system-level" or "true" async, because the application fully relies on the OS to execute actual I/O.
An example will help you understand the difference between Reactor and Proactor. We will focus on the read operation here, as the write implementation is similar. Here's a read in Reactor:
- An event handler declares interest in I/O events that indicate readiness for read on a particular socket
- The event demultiplexor waits for events
- An event comes in and wakes-up the demultiplexor, and the demultiplexor calls the appropriate handler
- The event handler performs the actual read operation, handles the data read, declares renewed interest in I/O events, and returns control to the dispatcher
By comparison, here is a read operation in Proactor (true async):
- A handler initiates an asynchronous read operation (note: the OS must support asynchronous I/O). In this case, the handler does not care about I/O readiness events, but is instead registers interest in receiving completion events.
- The event demultiplexor waits until the operation is completed
- While the event demultiplexor waits, the OS executes the read operation in a parallel kernel thread, puts data into a user-defined buffer, and notifies the event demultiplexor that the read is complete
- The event demultiplexor calls the appropriate handler;
- The event handler handles the data from user defined buffer, starts a new asynchronous operation, and returns control to the event demultiplexor.
...
As we mentioned, the true async Proactor pattern requires operating-system-level support.
”
注意上边提到的"The I/O operation ..."指的是真正的I/O操作,如对读缓冲区的读。
关于proactor还可以参考另一文档Proactor。
下边内容摘录自博文libevent源码深度剖析:Reactor模式:
Reactor的事件处理机制
首先来回想一下普通函数调用的机制:程序调用某函数,函数执行,程序等待,函数将结果和控制权返回给程序,程序继续处理。Reactor释义“反应堆”,是一种事件驱动机制。和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。使用Libevent也是向Libevent框架注册相应的事件和回调函数;当这些时间发生时,Libevent 会调用这些回调函数处理相应的事件(I/O读写、定时和信号)。
用“好莱坞原则”来形容Reactor再合适不过了:不要打电话给我们,我们会打电话通知你。举个例子:你去应聘某xx公司,面试结束后。“普通函数调用机制”公司HR比较懒,不会记你的联系方式,那怎么办呢,你只能面试完后自己打电话去问结果;有没有被录取啊,还是被拒了;“Reactor”公司HR就记下了你的联系方式,结果出来后会主动打电话通知你:有没有被录取啊,还是被拒了;你不用自己打电话去问结果,事实上也不能,你没有HR的联系方式。
Reactor模式的优点
Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:
- 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
- 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
- 可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
- 可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;
Reactor模式框架
使用Reactor模型,必备的几个组件:事件源、Reactor框架、多路复用机制和事件处理程序,先来看看Reactor模型的整体框架,接下来再对每个组件做逐一说明。
- 1. 事件源
- Linux上是文件描述符,Windows上就是Socket或者Handle了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如I/O事件。
- 2. event demultiplexer——事件多路分发机制
- 由操作系统提供的I/O多路复用机制,比如select和epoll。程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上;当有事件到达时,event demultiplexer会发出通知“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。
- 对应到libevent中,依然是select、poll、epoll等,但是libevent使用结构体eventop进行了封装,以统一的接口来支持这些I/O多路复用机制,达到了对外隐藏底层系统机制的目的。
- 3. Reactor——反应器
- Reactor,是事件管理的接口,内部使用event demultiplexer注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。对应到libevent中,就是event_base结构体。
- 一个典型的Reactor声明方式:
-
1 class Reactor 2 { 3 public: 4 int register_handler(Event_Handler *pHandler, int event); 5 int remove_handler(Event_Handler *pHandler, int event); 6 void handle_events(timeval *ptv); 7 // ... 8 };
- 4. Event Handler——事件处理程序
- 事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。对应到libevent中,就是event结构体。下面是两种典型的Event Handler类声明方式,二者互有优缺点。
-
1 class Event_Handler 2 { 3 public: 4 virtual void handle_read() = 0; 5 virtual void handle_write() = 0; 6 virtual void handle_timeout() = 0; 7 virtual void handle_close() = 0; 8 virtual HANDLE get_handle() = 0; 9 // ... 10 }; 11 class Event_Handler 12 { 13 public: 14 // events maybe read/write/timeout/close .etc 15 virtual void handle_events(int events) = 0; 16 virtual HANDLE get_handle() = 0; 17 // ... 18 };
Reactor事件处理流程
前面说过Reactor将事件流“逆置”了,那么使用Reactor模式后,事件控制流是什么样子呢?可以参见下面的序列图: