libevent 源码深度剖析 05¶
libevent 的核心:事件 event
对事件处理流程有了高层的认识后,本节将详细介绍 libevent 的核心结构 event,以及 libevent 对 event 的管理。
1. libevent 的核心 -event¶
libevent 是基于事件驱动(event-driven)的,从名字也可以看到 event 是整个库的核心。event 就是 Reactor 框架中的事件处理程序组件;它提供了函数接口,供 Reactor 在事件发生时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄。
首先给出 event 结构体的声明,它 位于 event.h 文件 中:
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
unsigned int min_heap_idx; /* for managing timeouts */
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
ev_events:event 关注的事件类型,它可以是以下 3 种类型:
-
I/O 事件: EV_WRITE 和 EV_READ 定时事件:EV_TIMEOUT 信号: EV_SIGNAL 辅助选项:EV_PERSIST,表明是一个永久事件 Libevent 中的定义为:
#define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 /* Persistant event */
可以看出事件类型可以使用“|”运算符进行组合,需要说明的是,信号和 I/O 事件不能同时设置;
还可以看出 libevent 使用 event 结构体将这 3 种事件的处理统一起来;
-
ev_next,ev_active_next 和 ev_signal_next 都是双向链表节点指针;它们是 libevent 对不同事件类型和在不同的时期,对事件的管理时使用到的字段。 libevent 使用双向链表保存所有注册的 I/O 和 Signal 事件
-
ev_next 就是该 I/O 事件在链表中的位置;称此链表为“已注册事件链表”;
- 同样 ev_signal_next 就是 signal 事件在 signal 事件链表中的位置;
- ev_active_next:libevent 将所有的激活事件放入到链表 active list 中,然后遍历 active list 执行调度,ev_active_next 就指明了 event 在 active list 中的位置;
- min_heap_idx 和 ev_timeout,如果是 timeout 事件,它们是 event 在小根堆中的索引和超时值,libevent 使用小根堆来管理定时事件,这将在后面定时事件处理时专门讲解;
- ev_base 该事件所属的反应堆实例,这是一个 event_base 结构体,下一节将会详细讲解;
- ev_fd,对于 I/O 事件,是绑定的文件描述符;对于 signal 事件,是绑定的信号;
-
ev_callback,event 的回调函数,被 ev_base 调用,执行事件处理程序,这是一个函数指针,原型为:
1void (*ev_callback)(int fd, short events, void *arg)
其中参数 fd 对应于 ev_fd;events 对应于 ev_events;arg 对应于 ev_arg;
-
ev_arg:void*,表明可以是任意类型的数据,在设置 event 时指定;
-
eb_flags:libevent 用于标记 event 信息的字段,表明其当前的状态,可能的值有:
1#define EVLIST_TIMEOUT 0x01 // event在time堆中 2 3#define EVLIST_INSERTED 0x02 // event在已注册事件链表中 4 5#define EVLIST_SIGNAL 0x04 // 未见使用 6 7#define EVLIST_ACTIVE 0x08 // event在激活链表中 8 9#define EVLIST_INTERNAL 0x10 // 内部使用标记 10 11#define EVLIST_INIT 0x80 // event已被初始化
-
ev_ncalls:事件就绪执行时,调用 ev_callback 的次数,通常为 1;
- ev_pncalls:指针,通常指向 ev_ncalls 或者为 NULL;
- ev_res:记录了当前激活事件的类型;
2. libevent 对 event 的管理¶
从 event 结构体中的 3 个链表节点指针和一个堆索引出发,大体上也能窥出 libevent 对 event 的管理方法 了,可以参见下面的示意图:
- 每次当 有事件 event 转变为就绪状态 时,libevent 就会把它移入到 active event list[priority] 中,其中 priority 是 event 的优先级;
- 接着 libevent 会根据自己的调度策略选择就绪事件,调用其 cb_callback() 函数执行事件处理;并根据就绪的句柄和事件类型填充 cb_callback 函数的参数。
3. 事件设置的接口函数¶
要向 libevent添加一个事件,需要 首先设置 event 对象,这通过调用 libevent 提供的函数有:event_set(), event_base_set(), event_priority_set() 来完成;下面分别进行讲解。
void event_set(struct event *ev, int fd, short events, void (*callback)(int, short, void *), void *arg)
- 设置事件 ev 绑定的 文件描述符或者信号,对于定时事件,设为 -1 即可;
- 设置事件类型,比如 EV_READ|EV_PERSIST, EV_WRITE, EV_SIGNAL 等;
- 设置 事件的 回调函数以及参数 arg;
- 初始化其它字段,比如缺省的 event_base 和优先级; int event_base_set(struct event_base base, struct event ev) 设置 event ev 将要注册到的 event_base; libevent 有一个 全局 event_base 指针 current_base,默认情况下事件 ev 将被注册到 current_base 上,使用该函数可以指定不同的 event_base; 如果一个进程中存在多个 libevent 实例,则必须要调用该函数为 event 设置不同的 event_base;
int event_priority_set(struct event *ev, int pri)
设置 event ev 的优先级,没什么可说的,注意 的一点就是:当 ev 正处于就绪状态时,不能设置,返回 -1。
4. 小结¶
本节讲述了 libevent 的核心 event 结构,以及 libevent 支持的事件类型和 libevent 对 event 的管理模型;接下来将会描述 libevent 的事件处理框架,以及其中使用的重要的结构体 event_base。