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

本文将从一个使用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中非常恶心的信号集成处理.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值