在event_base的初始化函数event_base_new_with_config中,会遍历eventops数组,选择其中符合要求的IO复用机制,然后退出遍历过程,这样event_base就选择了一个后端的IO复用机制,比如Libevent在Linux下默认是使用epoll的。
for (i = 0; eventops[i] && !base->evbase; i++) { // ... /* also obey the environment variables */ if (should_check_environment && event_is_method_disabled(eventops[i]->name)) continue; /* base->evsel记录后端IO复用机制 */ base->evsel = eventops[i]; /* 指向IO复用机制真正存储的数据,它通过evsel成员的init函数来进行初始化 */ /* 比如epoll时,evbase指向的是epollop */ base->evbase = base->evsel->init(base); }
到这里为止,Libevent已经初始化好了一种后台IO复用机制技术,这里以epoll为例,其他IO复用技术流程也类似。
2 Libevent的定时事件原理Libevent的定时事件也是要"加入"到Libevent中的IO复用框架中的,比如我们需要定时5秒钟,那么等到5秒钟之后就可以执行对应设置的回调函数了。以下是使用Libevent实现一个简单的定时器应用:
#include <iostream> #include <event.h> #include <event2/http.h> using namespace std; // Time callback function void onTime(int sock, short event, void *arg) { static int cnt = 0; cout << "Game Over! " << cnt++ << endl; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; if (cnt < 5) { // Add timer event event_add((struct event *) arg, &tv); } else { cout << "onTime is over" << endl; } } int main(int argc, char **argv) { cout << event_get_version() << endl; struct event_base *base = event_init(); struct event ev; evtimer_set(&ev, onTime, &ev); struct timeval timeevent; timeevent.tv_sec = 1; timeevent.tv_usec = 0; event_add(&ev, &timeevent); // Start event loop event_base_dispatch(base); event_base_free(base); return 0; }
定时器事件会被加入到一个时间堆(小堆结构)中,每次执行事件等待函数时,对于epoll来说就是epoll_wait函数了,把时间堆上最小节点的时间值赋值给该函数,这样如果有事件来临或者是时间超时了,都会返回。然后判断当前时间和调用事件等待函数之前的时间差是否大于或等于时间堆上最小节点的时间值,如果条件成立就执行对应的时间回调函数,这样就完成了一个定时事件。下面代码就是在事件监听循环中的部分代码。
while (!done) { base->event_continue = 0; /* Terminate the loop if we have been asked to */ if (base->event_gotterm) { break; } if (base->event_break) { break; } timeout_correct(base, &tv); /* 校准系统时间 */ tv_p = &tv; if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) { /* 获取时间堆上堆顶元素的超时值,即IO复用系统调用本次应该设置的超时值 */ timeout_next(base, &tv_p); } else { /* * if we have active events, we just poll new events * without waiting. */ /* 如果有就绪事件尚未处理,则将IO复用系统调用的超时时间置0 * 这样IO复用系统调用就直接返回,程序也就可以直接处理就绪事件了 */ evutil_timerclear(&tv); } /* If we have no events, we just exit */ /* 如果event_base中没有注册任何事件,则直接退出事件循环 */ if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) { event_debug(("%s: no events registered.", __func__)); retval = 1; goto done; } /* update last old time */ gettime(base, &base->event_tv); /* 更新系统时间 */ clear_time_cache(base); /* 调用事件多路分发器的dispatch方法等待事件 */ res = evsel->dispatch(base, tv_p); // 超时时间返回值为0 if (res == -1) { event_debug(("%s: dispatch returned unsuccessfully.", __func__)); retval = -1; goto done; } update_time_cache(base); /* 将系统缓存更新为当前系统时间 */ timeout_process(base); /* 检查时间堆上的到期事件并以此执行之 */ if (N_ACTIVE_CALLBACKS(base)) { /* 调用event_process_active函数依次处理就绪的信号事件和IO事件 */ /* 这里也可能有定时事件 */ int n = event_process_active(base); if ((flags & EVLOOP_ONCE) && N_ACTIVE_CALLBACKS(base) == 0 && n != 0) done = 1; } else if (flags & EVLOOP_NONBLOCK) done = 1; }
参考: