Libevent 是一个轻量级的高性能网络库,事件驱动,基于 Reactor 模式。
对 Libevent 源码的阅读可围绕事件管理展开:

Libevent 对三类事件源进行了统一,用 event
结构体表示,每个 event
实例表示一个事件。
同时监听多个事件(比如监听多个 socket 上的可读事件),必然会有多个 event
实例,对这些 event
实例进行管理需要特定的数据结构,Libevent 使用了尾队列(本质上是双向链表)和小根堆管理所有事件。

各个数据结构的用途如下:
- 已注册事件队列:所有事件添加时都会被插入到已注册事件队列
- 激活事件队列:事件触发后,被插入激活事件队列
- 信号事件队列:信号事件添加时会插入信号事件队列
- 小根堆:定时器事件用小根堆管理,堆顶的定时器事件是超时时间最小的(最先超时)
向 Reactor(对应源码中的 event_base
) 中添加I/O、定时事件,需要调用 event_add
函数,信号事件调用 evsignal_add
,evsignal_add
最终也会调用 event_add
:
event_add
min_heap_reserve: 为定时事件分配堆空间
add: 注册事件到 I/O 复用
event_queue_insert: event 插入已注册队列
event_queue_remove: 如果定时事件已在小根堆中, 删除堆中旧的事件
event_queue_remove: 如果定时事件已在激活队列中, 从激活队列中删除该事件
evutil_timeradd: 计算超时时间
event_queue_insert: 插入小根堆
可以看到,所有被注册的事件(I/O、定时器、信号)都会插入已注册队列。除此之外,定时事件被插入小根堆,信号事件被插入信号事件链表(在 evsignal_add
函数中完成)。
当事件激活(即所监听的事件发生),该事件会被插入激活队列,随后激活队列中的事件被处理(事件处理函数被调用),这部分工作在事件循环中完成:
event_base_dispatch
event_base_loop
timeout_correct: 校正系统时间
timeout_next: 计算下一个超时时间
evutil_timerclear: 如果有未处理的激活事件, 让 I/O 复用立即返回, 不必等待
event_haveevents: 没有已注册事件, 则立刻退出
gettime: 时间缓存 -> event_tv
dispatch: 把就绪事件插入激活队列
gettime: 系统时间 -> 时间缓存
timeout_process: 将超时的定时事件插入激活队列中
event_process_active: 处理激活队列中的事件
epoll_wait
evsignal_process: 处理信号事件
event_active: 将就绪事件添加到激活队列
event_queue_remove: 若是永久事件, 从激活队列中移除
event_del: 若是非永久事件, 从所有相关链表中删除
callback: 调用事件回调函数
事件循环的函数调用关系稍显复杂,但其主要逻辑很简单:
- dispatch 函数调用 epoll_wait(只讨论使用了 epoll 的情况) 等待 I/O 事件发生
- epoll_wait 返回后,处理信号事件(epoll_wait 返回有可能是因为发生了信号事件,这和 Libevent 对信号事件的管理有关,详见信号事件管理)
- 将就绪的 I/O 事件添加到激活事件队列
- 将超时事件添加到激活事件队列
- event_process_active 统一处理激活事件队列中的事件(即调用每个事件对应的事件处理函数)