读了一下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,则处理定时事件,可能会有多个定时器超时,所以用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以内。