libevent0.1&0.2源码理解(二)主要函数

修改记录:
3-29 初稿

介绍完类型与变量之后,就可以开始看程序的主轴了。我们从使用event-test.c入手可以看到的是:(略去之前创建命名管道和socket)
	/* Initalize the event library */
event_init();

/* Initalize one event */
event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);

/* Add it to the active events, without a timeout */
event_add(&evfifo, NULL);

event_dispatch();


首先是[b]event_init[/b]进行了全体初始化。
在0.1中这个函数很简单,就是把之前的四个全局队列头结点置空。
在0.2中,除了初始化队列之外,选择第一个系统支持的函数处理程序,其顺序是kqueue, event_ports, epoll, poll, select(存疑。没去看现在的顺序是怎样,但肯定不是随机选择的)
在找到了合适的程序支持,比如发现了select函数可以使用之后,把select的结构体selectop的地址传给evbase。selectop的结构体是
struct selectop {
int event_fds; /* fd_set最大值 */
int event_fdsz; //fd_set的大小
fd_set *event_readset;
fd_set *event_writeset;
} sop;


其次是用[b]event_set[/b]初始化一个事件
event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);

这一步也很简单,其实就是把socket以及事件、标志通通给结构体evfifo赋值,没有其它东西。

再次是用[b]event_add[/b]给之前初始化的事件配置定时器
第一步先看event_add之时传入的时间是否为空,如果不为空的话已现在的时间为准,并加上传入的时间作为结束时间。
第二步是看event中的标志是否是EVLIST_TIMEOUT,如果是EVLIST_TIMEOUT则先从timequeue中移除此事件,这个标志
第三步是从头遍历timequeue,按照从近到远的顺序找到event该被插入的位置。
第四步是给event挂上EVLIST_TIMEOUT的标志
第五步是判断libevent是否正在循环中,如果在而且标志了EVLIST_ADD,则返回;否则插进addqueue队尾,并标志EVLIST_ADD。可以看出EVLIST_ADD表明此event是否已经added进addqueue。我们可以把addqueue理解成待处理事件。如果不在循环中,则调用[b]event_add_post(ev)[/b]——
在0.1中,事件被插入队列后会被标记上EVLIST_READ或者EVLIST_WRITE;
而在0.2中,readqueue和writequeue被合并成了eventqueue,并统一标记为EVLIST_INSERTED

注:LOG_DBG是log.h中的一个宏,用于记录日志

最后是进入处理循环[b]event_dispatch[/b]
//这个是0.2版本的处理循环
while (1) {
timeout_next(&tv);

event_inloop = 1;
res = evsel->dispatch(evbase, &tv);
event_inloop = 0;

if (res == -1)
return (-1);

maxfd = 0;
for (ev = TAILQ_FIRST(&addqueue); ev;
ev = TAILQ_FIRST(&addqueue)) {
TAILQ_REMOVE(&addqueue, ev, ev_add_next);
ev->ev_flags &= ~EVLIST_ADD;

event_add_post(ev);

if (ev->ev_fd > maxfd)
maxfd = ev->ev_fd;
}

if (evsel->recalc(evbase, maxfd) == -1)
return (-1);

timeout_process();
}


dispatch是重中之重,里面进行的操作如下
[list]
[*]重新计算fd_set也就是readset或者sop->event_readset变量所需要的空间大小,并把fd_set清零。
[*]把readqueue与writequeue中的所有事件的套接字放入fd_set中。
[*]从timequeue队列中取出第一个定时器到时时间tv。在tv时间之前select读写的fd_set
[*]锁上循环区(event_inloop=1),一一取readqueue或eventqueue的事件,先把这些事件从所有队列中移除之后,在一一调用各事件的回调函数即[b]fifo_read[/b]。传入fifo_read的参数包括该事件的指针。fifo_read中再次调用event_set把event加入队列中,下次循环便会继续监听此事件。
[*]解锁循环区(event_inloop=0),把addqueue中的event全部取出来,放进readqueue或者0.2版本中的eventqueue
[*]调用recalc重新计算fd_set,也就是2中的步骤
[*]最后,对照当前时间,把timequeue队列中还没有超时的事件取出来调用回调函数。
[/list]
注:0.1和0.2版本的处理顺序有所不同(在进入循环区前后所调用的东西不同。0.1是select之后再循环,0.2反之),但基本上一致

至此除event_pending外的主要函数全部介绍完毕(event_pending主要用来检查某个事件是否已被标记在计划中)

总结:
libevent0.x主要使用了Tail Queue这一数据结构存放系统各种待处理的事件队列。(现在觉得这种类型蛮奇葩的,用双向链表应该也是可以,不知Niels当初有何考量)其中一个事件(event)包含了对应的套接字和当时队列中下一个事件的指针。
每次在回调之后,都需要重新把事件加入到队列中,这是因为每次select的对象都是队列。
在0.1的一个循环中,程序先处理readqueue再处理writequeue。在0.2中,libevent把read和write一视同仁,都合并成了eventqueue
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值