- event-internal.h中定义了eventop结构体,每一种IO多路复用机制都会实现eventop结构体中的五个方法。
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*);
/* set if we need to reinitialize the event base */
int need_reinit;
};
- select.c、epoll.c、poll.c中分别实现了eventop结构体中的五个方法,对应了select、epoll、poll这三种IO多路复用机制。以epoll模型为例,其实现如下所示:
const struct eventop epollops = {
"epoll",
epoll_init,
epoll_add,
epoll_del,
epoll_dispatch,
epoll_dealloc,
1 /* need reinit */
};
// epoll_init/epoll_add等是具体的函数实现
- 注意到以上函数中中的
void* arg
参数,这是一个IO多路复用模型上下文,通过epoll_init函数初始化得到。其定义如下:
// IO多路复用模型上下文
struct epollop {
// fds指向一个struct evepoll数组
// 对应的event管理,通过fds[sock_fd]得到与socket关联的evepoll
//
struct evepoll* fds;
// fds的数量
int nfds;
// epoll相关
struct epoll_event* events;
// struct epoll_event结构体的大小
int nevents;
int epfd /*epoll_create(32000)*/;
};
- 各个函数的功能主要是:
- epoll_init:这个函数会在初始化事件处理框架event_base时,根据不同的操作系统选择IO多路复用机制时使用。函数核心功能是使用epoll_create创建epoll实例、初始化表示IO多路复用模型上下文的结构体epollop
static void* epoll_init(struct event_base* base) { int epfd; struct epollop* epollop; /* Disable epollueue when this environment variable is set */ if (evutil_getenv("EVENT_NOEPOLL")) return (NULL); /* Initalize the kernel queue */ if ((epfd = epoll_create(32000)) == -1) { if (errno != ENOSYS) event_warn("epoll_create"); return (NULL); } FD_CLOSEONEXEC(epfd); if (!(epollop = calloc(1, sizeof(struct epollop)))) return (NULL); epollop->epfd = epfd; /* Initalize fields */ epollop->events = malloc(INITIAL_NEVENTS * sizeof(struct epoll_event)); if (epollop->events == NULL) { free(epollop); return (NULL); } epollop->nevents = INITIAL_NEVENTS; epollop->fds = calloc(INITIAL_NFILES, sizeof(struct evepoll)); if (epollop->fds == NULL) { free(epollop->events); free(epollop); return (NULL); } epollop->nfds = INITIAL_NFILES; // 使用socketpair函数创建一对套接字ev_signal_pair[0]和ev_signal_pair[1] // 初始化ev_signal事件:监听ev_signal_pair[1]上的读事件,并设置读事件发生的回调 evsignal_init(base); return (epollop); }
- epoll_add:重新初始化event_base时使用。主要功能是对一些变量的重新赋值
- epoll_dispatch:主要功能是调用epoll_wait检测epoll树中是否有就绪的文件描述符,如果有则将就绪文件描述符上的事件加入激活队列
static int epoll_dispatch(struct event_base* base, void* arg, struct timeval* tv) { struct epollop* epollop = arg; struct epoll_event* events = epollop->events; struct evepoll* evep; int i, res, timeout = -1; // 得到毫秒msecond if (tv != NULL) timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { /* Linux kernels can wait forever if the timeout is too big; * see comment on MAX_EPOLL_TIMEOUT_MSEC. */ timeout = MAX_EPOLL_TIMEOUT_MSEC; } res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); if (res == -1) { if (errno != EINTR) { event_warn("epoll_wait"); return (-1); } // 产生中断,处理信号 evsignal_process(base); return (0); } else if (base->sig.evsignal_caught) { // 捕捉到信号 evsignal_process(base); } event_debug(("%s: epoll_wait reports %d", __func__, res)); // 处理socket读写 for (i = 0; i < res; i++) { int what = events[i].events; struct event* evread = NULL, *evwrite = NULL; int fd = events[i].data.fd; // 非法fd if (fd < 0 || fd >= epollop->nfds) continue; evep = &epollop->fds[fd]; if (what & (EPOLLHUP | EPOLLERR)) { evread = evep->evread; evwrite = evep->evwrite; } else { if (what & EPOLLIN) { evread = evep->evread; } if (what & EPOLLOUT) { evwrite = evep->evwrite; } } if (!(evread || evwrite)) continue; if (evread != NULL) // 将事件放入激活队列 event_active(evread, EV_READ, 1); if (evwrite != NULL) event_active(evwrite, EV_WRITE, 1); } // 扩容 if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) { /* We used all of the event space this time. We should be ready for more events next time. */ int new_nevents = epollop->nevents * 2; struct epoll_event* new_events; new_events = realloc(epollop->events, new_nevents * sizeof(struct epoll_event)); if (new_events) { epollop->events = new_events; epollop->nevents = new_nevents; } } return (0); }
- epoll_del:对于信号事件,调用evsignal_del删除;对于IO读写事件则调用epoll_ctl取消检测指定fd的读写事件
- epoll_dealloc:资源释放。该函数会在调用
event_base_free
时使用
- 事件处理框架event_base中存在一个成员
const struct eventop* evsel;
表示Libevent选择的IO多路复用机制。在使用event_base_new函数对event_base进行初始化时,会选择一个具体的IO复用机制。
#ifdef HAVE_EVENT_PORTS
extern const struct eventop evportops;
#endif
#ifdef HAVE_SELECT
extern const struct eventop selectops;
#endif
#ifdef HAVE_POLL
extern const struct eventop pollops;
#endif
#ifdef HAVE_EPOLL
extern const struct eventop epollops;
#endif
#ifdef HAVE_WORKING_KQUEUE
extern const struct eventop kqops;
#endif
#ifdef HAVE_DEVPOLL
extern const struct eventop devpollops;
#endif
#ifdef WIN32
extern const struct eventop win32ops;
#endif
// 以上的struct eventop成员都在select.c、epoll.c、poll.c等文件中定义
/* In order of preference */
static const struct eventop* eventops[] = {
#ifdef HAVE_EVENT_PORTS
& evportops,
#endif
#ifdef HAVE_WORKING_KQUEUE
& kqops,
#endif
#ifdef HAVE_EPOLL
& epollops,
#endif
#ifdef HAVE_DEVPOLL
& devpollops,
#endif
#ifdef HAVE_POLL
& pollops,
#endif
#ifdef HAVE_SELECT
& selectops,
#endif
#ifdef WIN32
& win32ops,
#endif
NULL
};
// event_base_new方法内部
base->evbase = NULL;
for (i = 0; eventops[i] && !base->evbase; i++) {
base->evsel = eventops[i];
// 初始化IO多路复用模型上下文
base->evbase = base->evsel->init(base);
}
综上:Libevent通过一个const struct eventop* evsel;
指针实现对不同平台的IO多路复用机制提供统一的支持方式。不同平台会实现eventop结构体中的函数指针,而Libevent选择何种IO多路复用机制在编译期间决定。