您的位置:首页 > 其它

libevent源码浅析: 事件处理框架

2013-03-14 16:56 232 查看
http://godorz.info/2011/02/the-annotated-libevent-sources-about-event-handling-framework/

文将从一个使用libevent的小例子出发,解释libevent处理事件的流程.

例子如下:

view
sourceprint?

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():

view
sourceprint?

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()代码如下:

view
sourceprint?

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()代码了:

view
sourceprint?

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()处理已就绪事件队列.

view
sourceprint?

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()代码:

view
sourceprint?

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中非常恶心的信号集成处理.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: