一、IO多路复用概念
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞,挂起线程,直到阻塞超时。
二、libevent的IO多路复用应用
libevent支持多种IO多路复用机制,保障了跨平台和性能。支持的IO多路复用包括:
| 机制 | 平台 |
|---|---|
| select | unix、linux |
| win32select | win |
| poll | unix、linux |
| epoll | unix、linux |
| kqueue | Mac OS |
| dev/poll | Solaris |
| evport | Solaris |
注意:mac os用的unix内核,安卓用的linux内核。libevent还支持win系统的iocp,不过iocp不是IO多路复用,而是异步IO。
三、libevent的IO多路复用接口抽象
libevent使用eventop结构体来抽象IO多路复用相关的接口,可以在不同平台下,选择合适的IO多路复用实现。event_base结构体里有个属性evsel指向eventop,evbase指向特定模式下的数据结构。evnetop结构体的属性和含义如下表:
| 属性 | 含义 |
|---|---|
| const char *name | 多路复用名称 |
| void *(*init)(struct event_base *) | 完成初始化,返回改模式下专用的结构体 |
| int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo) | 添加fd上的事件监听 |
| int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo) | 删除fd上的事件监听 |
| int (*dispatch)(struct event_base *, struct timeval *) | 检测哪些事件已激活,并执行event的激活回调函数 |
| void (*dealloc)(struct event_base *) | 当fork了进程时,调用dealloc来重初始化一些属性 |
| int need_reinit | 是否要重新初始化event_base |
| enum event_method_feature features | 当前机制支持的feature |
| size_t fdinfo_len | 额外数据结构的长度 |
具体的调用关系图,如下:

四、libevent的select封装
select相关的数据放在selectop结构体里,具体的定义如下:
| 属性 | 含义 |
|---|---|
| int event_fds | 最大的fd编号 |
| int event_fdsz | 集合数组的最大容量 |
| int resize_out_sets | 标记是否需要重算集合大小 |
| fd_set *event_readset_in | 监听read的fd集合 |
| fd_set *event_writeset_in | 监听write的fd结合 |
| fd_set *event_readset_out | 调用select前,会把event_readset_in的数据拷贝过来,避免select修改集合数据 |
| fd_set *event_writeset_out | 调用select前,会把event_writeset_in的数据拷贝过来,避免select修改集合数据 |
五、libevent封装的select源码
static void * select_init(struct event_base *base)
{
//初始化selectop,并返回该对象
}
static int select_dispatch(struct event_base *base, struct timeval *tv)
{
int res=0, i, j, nfds;
struct selectop *sop = base->evbase;
check_selectop(sop);
if (sop->resize_out_sets) {
fd_set *readset_out=NULL, *writeset_out=NULL;
size_t sz = sop->event_fdsz;
if (!(readset_out = mm_realloc(sop->event_readset_out, sz)))
return (-1);
sop->event_readset_out = readset_out;
if (!(writeset_out = mm_realloc(sop->event_writeset_out, sz))) {
/* We don't free readset_out here, since it was
* already successfully reallocated. The next time
* we call select_dispatch, the realloc will be a
* no-op. */
return (-1);
}
sop->event_writeset_out = writeset_out;
sop->resize_out_sets = 0;
}
memcpy(sop->event_readset_out, sop->event_readset_in,
sop->event_fdsz);
memcpy(sop->event_writeset_out, sop->event_writeset_in,
sop->event_fdsz);
nfds = sop->event_fds+1;
EVBASE_RELEASE_LOCK(base, th_base_lock);
res = select(nfds, sop->event_readset_out,
sop->event_writeset_out, NULL, tv);
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
check_selectop(sop);
if (res == -1) {
if (errno != EINTR) {
event_warn("select");
return (-1);
}
return (0);
}
event_debug(("%s: select reports %d", __func__, res));
check_selectop(sop);
i = random() % nfds;
for (j = 0; j < nfds; ++j) {
if (++i >= nfds)
i = 0;
res = 0;
if (FD_ISSET(i, sop->event_readset_out))
res |= EV_READ;
if (FD_ISSET(i, sop->event_writeset_out))
res |= EV_WRITE;

本文深入探讨了IO多路复用的概念,并详细分析了libevent如何在不同平台下应用和封装IO多路复用,包括select和epoll。libevent通过eventop结构体抽象接口,实现跨平台兼容性和高性能。同时,文章还介绍了libevent中select和epoll的具体封装细节及源码分析。
最低0.47元/天 解锁文章
277





