Linux_libevent_Reactor模式
内容
- IO框架库
- Reactor模式的IO框架库包含哪些组件
- libevent是一个轻量级的I/O框架库。
I/O框架库
I/O框架库以库函数的形式,封装了较为底层的系统调用。
各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Proactor模式实现,要么同时两种模式实现。
Reactor模式
Reactor模式
要求主线程(IO处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即向工作线程(逻辑单元)通知该事件。除此之外,主线程不做其他实质性的工作。即读写数据、接受新的连接、处理客户请求均在工作线程中完成。
工作流程
使用同步I/O模型(以epoll_wait
为例)实现的Reactor模式
的工作流程是:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件。 - 主线程调用
epoll_wait
等待socket
上有数据可读。 - 当
socket
上有数据可读时,epoll_wait
通知主线程。主线程则将socket
可读事件放入请求队列。 - 睡眠在请求队列上的某个工作线程被唤醒,它从
socket
读取数据,并处理客户请求,然后工作线程往epoll
内核事件表中注册该socket
上的写就绪事件。 - 主线程调用
epoll_wait
等待socket
可写。 - 当
socket
可写时,epoll_wait
通知主线程。主线程将socket
可写事件放入请求队列。 - 睡眠在请求队列上的某个工作线程被唤醒,它往
socket
上写入服务器处理客户请求的结果。
总结:主线程注册读事件,可读时,主线程放入请求队列;工作线程读数据,处理请求,工作线程注册写事件;可写时,主线程放入请求队列;工作线程写数据。
组件框架
基于Reactor模式的I/O框架库包含如下几个组件:
- 句柄(Handle)
- 事件多路分发器(Event Demultiplexer)
- 事件处理器(Event Handler)和具体的事件处理器(Concrete EventHandler)
- Reactor。
这些组件的关系如下图所示。
- 句柄
- 说白了就是文件描述符,句柄在windows上某个资源的id,因为libevent库是跨平台的,所以叫法容易混用。
- 事件多路分发器
- 事件的到来是随机的、异步的。比如我们无法预知程序何时收到一个客户连接请求,又亦或收到一个暂停信号,所以程序需要循环地等待判断有无事件产生,这就是事件循环。
- 在事件循环中,等待事件一般使用I/O复用技术来实现。I/O框架库一般将系统支持的各种I/O复用系统调用封装成统一的接口,称为事件多路分发器。因此事件多路分发器可以理解为封装了IO复用,提供了一个更便于使用的接口。
- 事件多路分发器的demultiplex方法是等待事件的核心函数,其内部调用的是select、poll、epoll_wait等函数。
- 事件多路分发器还需要实现
register_event
和remove_event
方法,以供调用者给事件多路分发器中添加事件和从中删除事件。
- 事件处理器和具体事件处理器
- 事件处理器执行事件对应的业务逻辑。它通常包含一个或多个
handle_event
回调函数,这些回调函数在事件循环中被执行。 - I/O框架库提供的事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器。因此,事件处理器中的回调函数一般被声明为虚函数,以支持用户的扩展。
- 此外,事件处理器一般还提供一个
get_handle
方法,它返回与该事件处理器关联的句柄。那么,事件处理器和句柄有什么关系?当事件多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的。因此,我们必须将事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器。
- 事件处理器执行事件对应的业务逻辑。它通常包含一个或多个
- Reactor是I/O框架库的核心。它提供的几个主要方法是:
handle_events
,该方法执行事件循环。重复过程:等待事件,然后依次处理所有就绪事件对应的事件处理器。register_handler
,该方法调用事件多路分发器的register_event
方法来给事件多路分发器中注册一个事件。remove_handler
,该方法调用事件多路分发器的remove_event
方法来删除事件多路分发器中的一个事件。
libevent
libevent支持的事件类型
1 |
编程流程
- 定义、创建框架示例
- 向框架示例注册、注销事件:指定具体哪个base、哪个描述符,哪种事件,绑定回调函数和参数
- 有哪些事件:IO事件(
fd
、EV_READ
、fun_cb
)、信号事件(sig
、EV_SIGNAL
、sig_cb
)、定时器事件(-1
、EV_TIMEOUT
、tv_cb
)
- 有哪些事件:IO事件(
- 开启事件循环,实际上就是框架底层调用
select/poll/epoll
- 事件发生之后,调用回调函数如
fun_cb
。
示例
1 |
|
上面的代码描述了使用Libevent库的主要逻辑:
- 调用
event_init
函数创建event_base
对象。一个event_base
相当于一个Reactor实例。 - 创建具体的事件处理器,并设置他们所从属的Reactor实例。本例中的**
evsignal_new
用于创建信号事件处理器,evtimer_new
**用于创建定时事件处理器,它们是定义在/include/event2/event.h
文件中的宏,代码如下。其中evtimer_new
的原型event_new
的第二个参数默认赋-1
,第三个参数默认赋0
。
1 |
- 回调函数的格式需要统一:
void fun_cb(int fd, short event, void* argc)
1 |
|
ctrl+c
终止进程的信号代号是2,但是信号事件并没有fd描述符,而是巧妙地复用了fd,写入信号代号。
编译测试
gcc
编译链接命令后需要加后缀-levent
。
MainServer
1 | class TcpServer; |
1 |
|