libevent的初衷就是设计一个跨平台的轻量级I/0框架,由于历史问题,各平台的I/O复用机制难以统一。因此,这部分处理跨平台的方法值得重点关注。
eventop在源码中定义如下:
static const struct eventop *eventops[]={
#ifdef HAVE_EVENT_PORTS
&evportops,
#endif
….
}
由此可见libevent通过宏来在编译期找出可用的复用机制。
其中的顺序也是大文章的。官方的文档中说明libevent中支持的复用机制 /dev/poll, kqueue(2), event ports, select(2), poll(2) and epoll(4).
libevent开发人员通过对各种机制的基准测试,根据性能高到低选择复用机制优先顺序如图所示:
从中也可以了解到不同平台机制的不统一。标准的 poll和 select却难以满足大规模架构的需要,具体可以参考Dan Kegel的 "The C10K problem"文档。
关于机制的采用,libevent采用的是函数指针的方法。
struct eventop {
const char *name; /**//*机制名称*/
void *(*init)(struct event_base *); /**//*初始化事件*/
int (*add)(void *, struct event *); /**//*添加事件*/
int (*del)(void *, struct event *); /**//* 删除事件*/
int (*dispatch)(struct event_base *, void *, struct timeval *) /**//* 调度事件 */
void (*dealloc)(struct event_base *, void *);/**//* 释放资源*/
int need_reinit;
};
每个eventop即对应一种IO复用机制,其中的每个函数指针都指向使用该机制对事件进行操作的方法。
比如对应epoll的eventop结构中:
1.void *(*init)(…)函数指针对应的是static void * epoll_init(…)
2.在epoll_init()里,首先对环境变量进行检测,发现没有epoll机制时立即返回NULL。
3.使用epoll_create(32000)指定了连接数目的上限为32000个,然后对epollop的各个成员所需资源进行分配。
4.最后调用libevent自身的信号初始化函数。
选择机制并将其初始化的过程十分简单:
for (i = 0; eventops[i] && !base->evbase; i++) {
base->evsel = eventops[i];
base->evbase = base->evsel->init(base);
}
遍历存储机制的eventops数组,按顺序依次尝试初始化,一种机制被成功初始化则立即跳出循环。当然,检测系统环境可用机制,选择哪种机制更合适,具体的复用机制如何使用,这一切的琐碎细节你都无需关心,使用时,只要调用event_init()函数即可。Libevent对各种复用机制的巧妙封装避免了开发者开发大规模架构时,处理跨平台时机制选择的苦恼。