libevent I/O复用超时时间

来源:互联网 发布:四大流量小生数据对比 编辑:程序博客网 时间:2024/06/11 01:10

读了一下libevent的部分代码,主要是timer,signal,epoll相关的,学习网络库如何处理定时器事件和信号

大多数网络模块的库应该都是这样实现的,很巧妙。


首先说timer,libevent通过一个小根堆结构来保存定时事件,堆顶元素是最近即将超时的时间,例如右5个定时器,分别在2S,1S,4S,7S,8S后超时,那么堆顶的元素就是1S的定时器,这有什么用呢?

因为不管是select/poll/epoll,监控文件描述符的时候都会设置一个超时间隔,我们恰好可以把堆顶元素的超时时间作为这个超时间隔。

还是上面的5个timer,在本次的epoll_wait中我们可以取出堆顶元素1S作为超时间隔,分以下两种情况:

1:如果1S内没有I/O事件,那么epoll_wait将在1S后超时。此时应该触发1S的定时事件。

2:如果1S内发生了I/O事件,那么epoll_wait返回时,1S的定时事件不应该触发

所以,每次epoll_wait返回时,我们就依次取出堆顶元素,如果超时,就处理超时事件。

伪代码:

while (1)
  {
    // 从timer_heap中计算本次epoll_wait的超时间隔
    int timeout = timer_heap->nearest_timeout(now);
    int nfds = epoll_wait(fd, events, events_size, timeout);
    ....
    timer_handler *handler = NULL;
    //如果handler != NULL,则<strong>处理</strong>定时事件,可能会有多个<strong>定时器</strong>超时,所以用while循环
    while ((hander = timer_heap->check_time_out(now)) != NULL)
      handler->process_timeout(now);

  }

再说说signal事件,这个也是转化为I/O事件,通过socketpair或者pipe都可以,也就是epoll_wait中监控管道的读端,当接受到信号时,信号处理程序通过管道的写端把信号写进去,从而触发I/O事件,读端把信号读出来。


太巧妙了,把定时器事件和signal事件都转化为I/O事件了。并且定时器也可以精确到ms级别。

我自己写了下,定时器还是相当精确的,误差在1ms以内。


下面的

图就给出了这一基本流程。

1 )  首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于前面第步骤

2 和3 ;

2 )  向libevent 添加该事件 event。对于定时事件,libevent 使用一个小根堆管理,key 为超

时时间;对于 Signal 和I/O 事件,libevent 将其放入到等待链表(wait list)中,这是一

个双向链表结构;

3 )  程序调用event_base_dispatch() 系列函数进入无限循环,等待事件,以select() 函数为例;

每次循环前libevent 会检查定时事件的最小超时时间 tv ,根据 tv 设置select() 的最大等

待时间,以便于后面及时处理超时事件;

当select() 返回后,首先检查超时事件,然后检查I/O 事件;

  11

Libevent 将所有的就绪事件,放入到激活链表中;

然后对激活链表中的事件,调用事件的回调函数执行事件处理;

libevent对event 的管理

从event 结构体中的 3 个链表节点指针和一个堆索引出发,大体上也能窥出 libevent 对

event的管理方法了,可以参见下面的示意图。每次当有事件event转变为就绪状态时,libevent 就会把它移入到active event list[priority]中,其中priority是event的优先级;

接着libevent 会根据自己的调度策略选择就绪事件,调用其cb_callback()函数执行事件

处理;并根据就绪的句柄和事件类型填充cb_callback 函数的参数。

 

0 0
原创粉丝点击