什么是 Libevent
Libevent 是一款基于 Reactor 模式实现,由事件驱动的高性能开源 I/O 框架库,它提供一组 API 并允许开发者为事件注册回调函数。当事件发生时,Libevent 实例会执行相应的回调函数。使用 Libevent 来实现网络服务器的事件循环检查框架,可以使开发者更加集中于程序的逻辑之上,减少了工作量,而且 Libevent 在稳定性,性能及可移植性方面有着出色的表现。基于 Libevent 框架库实现的著名软件有:高性能分布式的内存缓存软件 memcached 和 Google 浏览器的 Chromium 的 Linux 版本。
一个基于 Reactor 模式实现的 I/O 框架库大致上应当包含如下几个组件:

I/O 框架库组件
- 句柄(Handle):一个句柄通常代表了一个事件源,在 Linux 下, I/O 事件对应的句柄是文件描述符(fd),而信号事件对应的句柄是信号值
- 事件多路分发器(EventDemultiplexer):将系统支持的各种 I/O 复用的系统调用封装成统一的接口,称为事件多路分发器。一般包含了事件注册
register_event(),事件删除remove_event()和事件等待demultiplex()。其中demultiplex()是等待事件的核心函数,其内部调用的是select、poll、epoll_wait 等函数 - 事件处理器和具体事件处理器(EventHandler 和 ConcreteEventHandler):事件处理器是一个接口,其中包含了一个或多个
handle_event()方法。用户需要继承这一接口,并实现自己的事件处理器。 - Reactor:整个 I/O 框架库的核心,主要提供了以下几个方法:
-
handle_events():该方法执行事件循环,不断地等待事件,然后依次处理所有就绪事件对应的事件处理器 -
register_handler():调用事件多路分发器的register_event()方法来向事件多路分发器中注册一个事件 -
remove_handler():调用事件多路分发器的remove_event()方法来删除事件多路分发器中的一个事件
-

I/O 框架库的工作时序
Libevent 的源码链接:git clone https://github.com/libevent/libevent.git
Libevent 官网: http://libevent.org/
编译及安装方法(在 libevent 的源码目录下):
./configure
make
make verify # (optional)
sudo make install
一个简单的 libevent 示例代码:
1 | //helloworld.cpp |
编译方法:
g++ helloworld.cpp -levent_core -std=c++11
在上述的 Demo 中,包含了使用 Libevent 库的一个基本逻辑:
- 调用
event_init()创建 event_base 对象,一个 event_base 对象相当于一个 Reactor 实例- 创建具体的事件处理器,其中
evsignal_new()创建的是信号事件处理器和evtimer_new()创建的是定时事件处理器- 调用
event_add()将事件处理器插入到注册事件队列中,并将其对应的事件插入到事件多路分发器中,相当于 Reactor 中的register_handler方法- 调用
event_base_dispatch()执行事件循环- 循环结束后,调用
*_free()释放系统资源
接下来我们便按照上述 5 个步骤进行源码分析,出于篇幅考虑,会将这 5 个步骤拆分成 2 篇文章分别叙述,本篇只讨论前 2 个步骤。
event_init 函数的实现与相关的数据结构
在了解 event_int()函数做了何种操作之前,我们需要了解 event_base、eventop 这两个数据结构,它们都定义在 libevent-2.1.12-stable/event-internal.h 中。其中 event_base 构成了 Reactor 的角色, 而 eventop 则构成了 EventDemultiplexer 角色
1 | /** Structure to define the backend of a given event_base. */ |
正如先前所提到的那样,Libevent 向用户提供了统一的 I/O 复用接口,屏蔽了底层系统的之间的差异,而这一起都要通过定义在 event.c 文件中的 eventops 数组来实现。在不同的系统下有不同的优先级,对于 Linux 系统而言,默认使用的 I/O 复用技术是 epoll。
1 | //优先级 |
event_init()定义在 event.c 中,它的主要作用是执行 event_base_new_with_config(NULL)来创建一个 event_base 实例并将其保存在全局静态变量 current_base 当中。其中 ``event_base_new_with_config(NULL)`的参数为 NULL 代表根据程序的环境设置相应的 event_base 配置
1 | struct event_base *event_init(void) |
创建具体的事件处理器
在 Linux 服务器编程中,需要考虑的事件通常有三种:I/O 事件、信号以及定时器事件,而 Libevent 中采用了统一事件源的方式,将这三种事件利用 Unix 域套接字和 I/O 复用的系统调用统一转换成为 I/O 事件进行处理。统一对于信号和定时器,通过socketpair()创建一对 Unix 域套接字,这对套接字起到了一个全双工管道的作用,当信号或是超时事件发生时,通过管道将信号或超时事件发送至 Reactor 进行处理。
要了解 Libevent 的事件类型,我们需要了解位于 libevent-2.1.12-stable\include\event2\event_struct.h 中的 event 结构体和 event_callback 结构体。
1 | //TAILQ_ENTRY:尾队列中的节点类型 |
通过上述对 event 和 event_callback 的定义,我们对它们所起到的作用有了一定的了解。首先对于 event_callback 而言,它相当于一个 ConcreteEventHadler,其中主要的两个成员是 evcb_active_next,它指向了活动事件链表中的下一个节点,以及联合体成员 evcb_cb_union,它代表了某一个特定的回调函数。而对于 event 结构体而言,它相当于一个 EventHadler,其中主要的成员有具体的事件处理器 ev_evcallback、超时事件ev_timeout_pos、注册事件 ev_(其中包含了 I/O 事件 ev_io 和信号事件 ev_signal),以及句柄 ev_fd。
一旦了解了事件以及相应的处理器的定义后,我们就需要去看一看如何创建这两个数据结构,并将它们插入到注册事件列表中。event 的创建可以通过定义在 event.c 中的 event_new()来创建
1 | //evsignal_new 和 evtimer_new 都是定义在 include/event2/event.h 中的宏,都是调用了 event.c 中的 event_new() 来实现 |
参考资料:
《Linux 高性能服务器编程》 —— 游双著
《libevent-book》 —— Libevent 上的官方文档
下篇:Libevent 源码阅读笔记(二)、从一个简单例子开始(下)
- 本文作者: Phoenix
- 本文链接: https://phoenix500526.github.io/2020/08/10/Libevent-源码阅读笔记-一-从一个简单例子开始-上/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!