http://godorz.info/2011/02/the-annotated-libevent-sources-about-event-handling-framework/
文将从一个使用libevent的小例子出发,解释libevent处理事件的流程.
例子如下:
01.
static
void
fifo_read(
int
fd,
short
event,
void
*arg) {...}
02.
03.
int
main (
int
argc,
char
**argv)
04.
{
05.
int
socket = open (
"/tmp/event.fifo"
, O_RDONLY | O_NONBLOCK, 0);
06.
07.
fprintf
(stdout,
"Please write data to %s\n"
, fifo);
08.
09.
event_init();
10.
11.
struct
event evfifo;
12.
event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);
13.
14.
event_add(&evfifo, NULL);
15.
16.
event_dispatch();
17.
}
libevent库的使用方法大体上就像例子展示的那样,先由event_init()得到一个event_base实例(也就是反应堆实例),然后由 event_set()初始化一个event,接着用event_add()将event绑定到event_base,最后调用event_dispatch()进入时间主循环.
event_init()和event_set()功能都很简单,它们分别对event_base结构体和event结构体做初始化.我们直接看看event_add():
01.
//为了思路清晰,这里分析的是I/O事件,暂不考虑信号和定时器相关处理代码.
02.
03.
//函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明.如果注册成功,ev将被插入到已注册链表中.
04.
int
event_add(
struct
event *ev,
const
struct
timeval *tv)
05.
{
06.
//得到ev对应的反应堆实例event_base
07.
struct
event_base *base = ev->ev_base;
08.
09.
//得到libevent选择的已封装的I/O多路复用技术
10.
const
struct
eventop *evsel = base->evsel;
11.
12.
void
*evbase = base->evbase;
13.
int
res = 0;
14.
15.
//ev->ev_events表示事件类型
16.
//如果ev->ev_events是 读/写/信号 事件,而且ev不在 已注册队列 或 已就绪队列,
17.
//那么调用evbase注册ev事件
18.
if
((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
19.
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE)))
20.
{
21.
22.
//实际执行操作的是evbase
23.
res = evsel->add(evbase, ev);
24.
25.
//注册成功,把事件ev插入已注册队列中
26.
if
(res != -1)
27.
event_queue_insert(base, ev, EVLIST_INSERTED);
28.
}
29.
30.
return
(res);
31.
}
注释已经很明了了,如果一个事件不在已注册队列或者已激活队列,而且它是I/O事件或者信号事件,那么调用select_add()将ev插入到selectop的内部数据结构中(本文以select为例,下文不再说明.).select_add()代码如下:
01.
//略去信号处理的相关代码
02.
03.
static
int
select_add(
void
*arg,
struct
event *ev)
04.
{
05.
struct
selectop *sop = arg;
06.
07.
//如果是读类型事件,把该事件的文件描述符加入到selectop维护的读fd_set集
08.
//event_readset_in中,并且把该事件插入到读事件队列.
09.
if
(ev->ev_events & EV_READ)
10.
{
11.
FD_SET(ev->ev_fd, sop->event_readset_in);
12.
sop->event_r_by_fd[ev->ev_fd] = ev;
13.
}
14.
15.
//略去对写事件的处理
16.
}
小结一下,结合event_add()代码和select_add()代码,可以看出在调用event_add()时,事件将被插入其对应的反应堆实例event_base的已注册事件队列中,而且还会被加入到selectop维护的内部数据结构中进行监视.
现在可以看看event_dispatch()代码了:
01.
//略去信号事件和定时器事件处理的相关代码
02.
03.
int
event_dispatch(
void
)
04.
{
05.
return
(event_loop(0));
06.
}
07.
08.
int
event_loop(
int
flags)
09.
{
10.
return
event_base_loop(current_base, flags);
11.
}
12.
13.
//事件主循环
14.
int
event_base_loop(
struct
event_base *base,
int
flags)
15.
{
16.
const
struct
eventop *evsel = base->evsel;
17.
void
*evbase = base->evbase;
18.
struct
timeval *tv_p;
19.
int
res, done;
20.
21.
done = 0;
22.
while
(!done)
23.
{
24.
//从定时器最小堆中取出根节点,其时间值作为select最大等待时间
25.
//如果定时器最小堆没有元素,那么select最大等待时间为0
26.
timeout_next(base, &tv_p);
27.
28.
//调用select_dispatch(),它会将已经准备好的事件移到已就绪事件队列中
29.
res = evsel->dispatch(base, evbase, tv_p);
30.
31.
//有就绪事件了,那就处理就绪事件吧.
32.
if
(base->event_count_active)
33.
event_process_active(base);
34.
}
35.
}
event_base_loop()先从定时器最小堆中取出根节点作为select的最大等待时间,然后调用select_dispatch()将已经准备好的事件移到已就绪事件队列中,最后调用event_process_active()处理已就绪事件队列.
01.
//略去信号事件和定时器事件处理的相关代码
02.
03.
static
int
select_dispatch(
struct
event_base *base,
void
*arg,
struct
timeval *tv)
04.
{
05.
int
res, j;
06.
struct
selectop *sop = arg;
07.
08.
//根据前面对select_add()的解释,事件fd已被加入到fd_set集中进行监视.
09.
res = select(sop->event_fds + 1, sop->event_readset_out,
10.
sop->event_writeset_out, NULL, tv);
11.
12.
for
(j = 0, res = 0; j <= sop->event_fds; ++j, res = 0)
13.
{
14.
struct
event *r_ev = NULL, *w_ev = NULL;
15.
16.
//找出已经准备好读的事件
17.
if
(FD_ISSET(j, sop->event_readset_out))
18.
{
19.
r_ev = sop->event_r_by_fd[i];
20.
res |= EV_READ;
21.
}
22.
23.
//将已经准备好读的事件移到已就绪事件队列
24.
if
(r_ev && (res & r_ev->ev_events))
25.
event_active(r_ev, res & r_ev->ev_events, 1);
26.
27.
//略去对已经准备好写的事件的处理
28.
}
29.
}
看看在event_base_loop()中被调用的event_process_active()代码:
01.
static
void
event_process_active(
struct
event_base *base)
02.
{
03.
struct
event *ev;
04.
struct
event_list *activeq = NULL;
05.
int
i;
06.
short
ncalls;
07.
08.
//寻找最高优先级(priority值越小优先级越高)的已就绪事件队列
09.
for
(i = 0; i < base->nactivequeues; ++i)
10.
{
11.
if
(TAILQ_FIRST(base->activequeues[i]) != NULL)
12.
{
13.
activeq = base->activequeues[i];
14.
break
;
15.
}
16.
}
17.
18.
for
(ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq))
19.
{
20.
//如果有persist标志,则只从激活队列中移除此事件,
21.
if
(ev->ev_events & EV_PERSIST)
22.
event_queue_remove(base, ev, EVLIST_ACTIVE);
23.
24.
else
//否则则从激活事件列表,以及已注册事件中双杀此事件
25.
event_del(ev);
26.
27.
ncalls = ev->ev_ncalls;
28.
ev->ev_pncalls = &ncalls;
29.
30.
//每个事件的回调函数的调用次数
31.
while
(ncalls)
32.
{
33.
ncalls--;
34.
ev->ev_ncalls = ncalls;
35.
36.
//调用回调函数
37.
(*ev->ev_callback)((
int
)ev->ev_fd, ev->ev_res, ev->ev_arg);
38.
}
39.
}
40.
}
现在,看看这个被阉割的只考虑I/O事件的libevent主循环框架:
event_base_loop: while() { //从定时器最小堆取出select最大等待时间 //select出已准备事件,将它们移到已就绪事件队列中 //处理已就绪事件 }
这真是篇节能环保的文章啊,哈哈.因为libevent代码太恶心了,描述出来都觉得恶心,有空得拿来重构下..下篇文章会讲讲libevent中非常恶心的信号集成处理.