概述
libevent
是事件驱动, 同步而非阻塞的. 为了保证非阻塞我们在前面已经看到过多次了, 设置文件描述符为非阻塞. libevent内部也是为了满足非阻塞所以集成了一系列的多路复用函数, 但并非每个都会使用. libevent会根据不同系统选择不同的复用函数. 这里我们主要分析linux的复用机制(epoll).
统一接口
不管是在windows还是linux, 也不管使用select
还是epoll
都统一调用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;
};
而分发器调用的函数都是根据编译器选择默认设置.
static const struct eventop *eventops[] = {
#ifdef HAVE_EVENT_PORTS
&evportops,
#endif
#ifdef HAVE_WORKING_KQUEUE
&kqops,
#endif
#ifdef HAVE_EPOLL
&epollops,
35
#endif
#ifdef HAVE_DEVPOLL
&devpollops,
#endif
#ifdef HAVE_POLL
&pollops,
#endif
#ifdef HAVE_SELECT
&selectops,
#endif
#ifdef WIN32
&win32ops,
#endif
NULL
};
epollop 结构
该结构源码在epoll.c
中
struct epollop {
// 对应的event的管理, 由 fds[fd] 来管理具体哪一个文件描述符的 IO 操作
struct evepoll *fds;
int nfds; // fd 的数量
struct epoll_event *events; // epoll 的事件
int nevents; // 事件的数量
int epfd; // epoll_create 返回对应的文件描述符
};
// epoll IO
struct evepoll {
struct event *evread;
struct event *evwrite;
};
在epollops
默认初始化设置的5种函数都是对epoll
函数的封装而已. 下面我们就来具体分析这些函数的封装.
// epoll 对应的函数
const struct eventop epollops = {
"epoll",
epoll_init,
epoll_add,
epoll_del,
epoll_dispatch,
epoll_dealloc,
1 /* need reinit */
};
epoll_init 函数
// 初始化
// 调用 epoll_create 初始化 epoll 并且初始化 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 */
// 创建 epoll 连接
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;
// 并且对信号也设置初始化
evsignal_init(base);
return (epollop);
}
epoll_init
函数其实就是调用epoll_create
并且对描述符设置为非阻塞, 再对event分配返回epoll
事件的内存大小, 最后设置初始化标志并初始化信号.
epoll_add 函数
// 添加 epoll 事件
// 其实调用 epoll_ctl 选项的 EPOLL_CTL_MOD 和 EPOLL_CTL_ADD
static int
epoll_add(void *arg, struct event *ev)
{
struct epollop *epollop = arg;
struct epoll_event epev = {0, {0}};
struct evepoll *evep;
int fd, op, events;
// 如果事件是信号, 则添加到信号中
if (ev->ev_events & EV_SIGNAL)
return (evsignal_add(ev));
// 如果文件描述符大于了 nfds 时, 重新处理分配处理事件的空间大小
fd = ev->ev_fd;
if (fd >= epollop->nfds) {
/* Extent the file descriptor array as necessary */
if (epoll_recalc(ev->ev_base, epollop, fd) == -1)
return (-1);
}
// evep 是当前事件的 IO状态
evep = &epollop->fds[fd];
op = EPOLL_CTL_ADD;
events = 0;
// 如果事件本身可读(即 已经在epoll中), 所以设置方法为 EPOLL_CTL_MOD
if (evep->evread != NULL) {
events |= EPOLLIN;
op = EPOLL_CTL_MOD;
}
// 同理
if (evep->evwrite != NULL) {
events |= EPOLLOUT;
op = EPOLL_CTL_MOD;
}
// 如果事件是 EV_READ 表示是读事件
if (ev->ev_events & EV_READ)
events |= EPOLLIN;
// 事件事件是 EV_WRITE 表示是写事件
if (ev->ev_events & EV_WRITE)
events |= EPOLLOUT;
epev.data.fd = fd; // 设置监听的文件描述符
epev.events = events; // 设置当前的监听事件
if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1)
return (-1);
/* Update events responsible */
// 更新事件的监听状态, 之所以没有上面同时修改, 是要保证 epoll_ctl 调用成功
if (ev->ev_events & EV_READ)
evep->evread = ev;
if (ev->ev_events & EV_WRITE)
evep->evwrite = ev;
return (0);
}
epoll_del 删除事件
// 删除事件
static int
epoll_del(void *arg, struct event *ev)
{
struct epollop *epollop = arg;
struct epoll_event epev = {0, {0}};
struct evepoll *evep;
int fd, events, op;
int needwritedelete = 1, needreaddelete = 1;
// 如果是信号, 则交给信号处理函数处理
if (ev->ev_events & EV_SIGNAL)
return (evsignal_del(ev));
fd = ev->ev_fd;
if (fd >= epollop->nfds)
return (0);
evep = &epollop->fds[fd];
op = EPOLL_CTL_DEL;
events = 0;
if (ev->ev_events & EV_READ)
events |= EPOLLIN;
if (ev->ev_events & EV_WRITE)
events |= EPOLLOUT;
// 如果事件本身已经存在 epoll 中
if ((events & (EPOLLIN|EPOLLOUT)) != (EPOLLIN|EPOLLOUT)) {
// 如果是可写
if ((events & EPOLLIN) && evep->evwrite != NULL) {
needwritedelete = 0;
events = EPOLLOUT;
op = EPOLL_CTL_MOD;
// 如果是可读
} else if ((events & EPOLLOUT) && evep->evread != NULL) {
needreaddelete = 0;
events = EPOLLIN;
op = EPOLL_CTL_MOD;
}
}
epev.events = events;
epev.data.fd = fd;
if (needreaddelete)
evep->evread = NULL;
if (needwritedelete)
evep->evwrite = NULL;
if (epoll_ctl(epollop->epfd, op, fd, &epev) == -1)
return (-1);
return (0);
}
epoll_dispatch 函数
epoll_dispatch
分发事件, 这是epoll
封装的一个重要的东西.
// 分发事件
// 调用 epoll_wait 函数处理到来的事件. 并检查信号和事件的流量大小
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;
// 计算时间
if (tv != NULL)
timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000;
// 如果时间大于了最大 epoll 设置时间就设置为 epoll 最大时间
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;
}
// 调用 epoll_wait 等待事件到来
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) {
// 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号
evsignal_process(base);
}
event_debug(("%s: epoll_wait reports %d", __func__, res));
// 处理IO事件
for (i = 0; i < res; i++) {
// IO事件的类型
int what = events[i].events;
struct event *evread = NULL, *evwrite = NULL;
int fd = events[i].data.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;
// 触发 IO 操作
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_wait 等待事件到来
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) {
// 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号
evsignal_process(base);
}
在这里可以看到, epoll_wait
等待事件
- 如果被中断且中断源是信号, 则调用
evsignal_process
将信号加入就绪队列中 - 如果正常退出, 但是事件的信号标志被设置, 也将信号加入到就绪队列中.
总结
本节基本上没有做太多的分析, 因为基本都是关于网络编程的基础, 所以这里也就不花篇幅来分析.
- 在epoll_dispatch中执行epoll_wait, 并且通过返回后判断类型来决定是否将信号就如就绪队列.
最后
以上就是淡淡篮球为你收集整理的libevent IO多路复用之epoll的全部内容,希望文章能够帮你解决libevent IO多路复用之epoll所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复