上一节简单的介绍了事件的概念,这节就实际利用libevent的代码与上节最后说到的几个问题进行详细说明.为了简单考虑,这里先不考虑超时事件与信号事件,只进行读写事件的解释,遇到和超时事件信号事件有关的代码也就略过了.
1.事件的定义
事件是libevent一个非常重要的概念,先来看看它的数据结构定义.代码位于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;
};
上面的代码中,有几个是比较重要的地方.
1.还记得上节最后说过如果事件很多怎么办?就需要用一个链表将事件进行管理组合,这个链表咱们后续会说到.前三个TAILQ_ENTRY结构变量的作用就是用于标记当前事件位于链表中的位置.
2.event_base结构的变量ev_base暂时理解为事件链表的管理者就可以了.
3.ev_events是事件的类型,是读事件还是写事件等.
4.ev_callback回调函数是需要关注的重点,这个就是事件的处理方法,也是需要编程实现的地方.
5.ev_flags用于标记事件的状态,这个下一段解释.
6.还有一个重要的参数ev_fd,这个不用我说大家也看出来了:网络描述符.没有这个参数我们也不知道这个网络数据从那个端口来的.
2.事件的状态
在介绍如何初始化事件开始,介绍一下libevent内部对于事件的状态管理.先来看看其定义,位于event.h文件中
#define EVLIST_TIMEOUT 0x01
#define EVLIST_INSERTED 0x02
#define EVLIST_SIGNAL 0x04
#define EVLIST_ACTIVE 0x08
#define EVLIST_INTERNAL 0x10
#define EVLIST_INIT 0x80
这里对于读写事件来说,需要注意的是第二个,第四个和第六个.
首先事件应该有几种状态,初始化状态,等待发生状态,发生状态,完成状态.因此一个比较好的思路可以构建两个事件链表,一个是等待发生事件链表,另一个是发生事件链表.这个时候整个底层平台需要做的就很明确了.
1.不断的遍历等待发生事件链表,若触发条件达到之后将其放到发生事件链表中.
2.不断的遍历发生事件链表,调用事件响应函数处理.
3.事件完成之后就不需要保存了,没有必要,具体怎么处理后续再说.
根据上述想法,事件的状态转化路径如下:
1.当事件刚完成初始化时,状态为EVLIST_INIT.
2.当插入已注册链表等待事件发生时,状态为EVLIST_INSERTED.
3.当事件发生时,状态为EVLIST_ACTIVE.
4.当事件完成之后,就看程序员怎么处理了.
3.事件的初始化
现在介绍事件的初始化过程,重点是函数event_set,位于event.c文件中,具体见下.
void
event_set(struct event *ev, int fd, short events,
void (*callback)(int, short, void *), void *arg)
{
/* Take the current base - caller needs to set the real base later */
ev->ev_base = current_base;
ev->ev_callback = callback;
ev->ev_arg = arg;
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_res = 0;
ev->ev_flags = EVLIST_INIT;
ev->ev_ncalls = 0;
ev->ev_pncalls = NULL;
min_heap_elem_init(ev);
/* by default, we put new events into the middle priority */
if(current_base)
ev->ev_pri = current_base->nactivequeues/2;
}
这个函数是对事件进行初始化,主要是赋值事件处理函数,事件标志,状态等.可以看到初始事件的状态是EVLIST_INIT.这里current_base是一个全局的变量,理解为事件链表管理者就可以了.
4.注册添加事件
初始化完成事件后,需要将事件告知平台,这样平台才知道有这么个事件.重点看event_add函数,位于event.c文件,已删除部分无关代码int
event_add(struct event *ev, const struct timeval *tv)
{
struct event_base *base = ev->ev_base;
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
int res = 0;
/* 删除部分代码 */
/*当事件是读写事件或信号事件,并且事件并没有插入注册事件链表或激活事件链表中*/
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
/* evsel是第一节介绍过的使用的网络模型,这里是将整个事件的fd插入到网络模型中去. */
res = evsel->add(evbase, ev);
if (res != -1) /* 网络模型fd插入成功 */
event_queue_insert(base, ev, EVLIST_INSERTED);/*将当前事件插入链表中,并且改变事件的状态为EVLIST_INSERTED*/
}
/* 删除部分代码 */
return (res);
}
上面的注释很清除的解释了工作流程.后续的下一节再详细介绍!