在本小节中,我们将展开对定时事件的研究。首先还是和研究信号事件部分一样,先看看它是如何集成到多路I/O中的(或者说是如何与event_base联系起来的)。
由于seletc、poll、epoll这类多路I/O机制支持定时,所以将定时事件集成到主循环中比起信号事件容易的多。我们只需要将定时事件注册到小根堆上,然后根据堆顶(最短超时事件)来计算多路I/O机制需要等待的最大超时时间,这样超时之后,就可以处理就绪的定时事件了。
在主循环中,有这样一段代码:
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { /* 没有激活的事件并且是非阻塞时 */ timeout_next(base, &tv_p); } else { /* * if we have active events, we just poll new events * without waiting. */ evutil_timerclear(&tv); } ... res = evsel->dispatch(base, evbase, tv_p); ... timeout_process(base);timeout_next用于计算主循环最大的等待时间,这里将tv_p赋值为等待的时间。 然后evsel->dispatch调用具体的多路I/O机制的等待函数,比如epoll的epoll_wait,最后一个参数设置的定时时间就是timeout_next计算得出的时间。这样的话,就不用来一个定时事件就调用epoll_wait等待,而是将定时事件都集中在一起处理。
在这段代码中,有三个核心函数timeout_next、dispatch、timeout_process。接下来我们会介绍第一个和最后一个,dispatch放在关于多路I/O机制集成部分的小节讲。
该函数的作用就是计算需要等待的时间,步骤如下:
取得最快超时的时间将其与当前时间比较如若小于等与当前时间,证明已经超时了,将tv置成0,返回之后,dispatch会很快将其激活(等待时间都赋成0了)如若大于当前时间,证明还有段时间需要等待,调用evutil_timersub函数做个减法,将等待时间赋给tvtv指向tv_p,而tv_p用于给dispatch指定超时时间。
这个函数的逻辑也很简单:
把小根堆顶的事件的超时时间拿来和当前时间比较如果大于,则证明时间还没到,那么小根堆其余的事件也不可能超时了如果小于,证明该事件已经满足激活条件了(超时),将其激活重复查看新的小根堆顶是否满足条件,直至不满足条件或者堆中已经没有事件了。在本小节中,我们知道了定时事件是如何集成到一起让主循环去处理的,并且仔细看了timeout_next以及timeout_process这两个重要的函数,在下一小节中,我们将看到关于时间管理部分的函数,即timeout_correct还有时间的加减、获取等。