前言
libevent2.0和libevent2.1的接口大体一致,只是做了少量的优化,下面就分析一下两个版本的主要区别,一是定时器增加了timerfd;二是io事件的处理增加了EV_CLOSE标记用于专门处理连接关闭。
一、处理定时器事件
区别
libevent2.0中通过给epoll_wait设置超时值来处理定时器事件;libevent2.1增加了一种选择通过timerfd来监听定时器事件,timerfd是在Linux内核2.6.25版本中引入的一种系统调用,它允许通过文件描述符来获取定时器事件。
源码分析
libevent2.1增加timerfd来监听超时事件,event_add中调用epoll_init注册的回调函数让epoll监听timerfd,event_dispatch中调用epoll_dispatch设置timerfd的超时时间,超时时间到了就会触发epoll_wait监听的timerfd,这时就能处理一起返回的其他fd事件和定时器事件。
//创建timerfd事件并加入到epoll_wait中监听,timerfd并没有设置回调函数,他只是用来通知epoll_wait中有超时时间发生,然后将最小堆中的超时事件加入到激活事件队列中。
static void *
epoll_init(struct event_base *base)
{
int epfd = -1;
struct epollop *epollop;
if (epfd == -1) {
/* Initialize the kernel queue using the old interface. (The
size field is ignored since 2.6.8.) */
if ((epfd = epoll_create(32000)) == -1) {
if (errno != ENOSYS)
event_warn("epoll_create");
return (NULL);
}
evutil_make_socket_closeonexec(epfd);
}
...
#ifdef USING_TIMERFD
if ((base->flags & EVENT_BASE_FLAG_PRECISE_TIMER) &&
base->monotonic_timer.monotonic_clock == CLOCK_MONOTONIC) {
int fd;
fd = epollop->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); //timerfd 设置成非阻塞且子进程中自动关闭
if (epollop->timerfd >= 0) {
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.data.fd = epollop->timerfd;
epev.events = EPOLLIN;
if (epoll_ctl(epollop->epfd, EPOLL_CTL_ADD, fd, &epev) < 0) { //加入到epoll中监听
event_warn("epoll_ctl(timerfd)");
close(fd);
epollop->timerfd = -1;
}
} else {
if (errno != EINVAL && errno != ENOSYS) {
event_warn("timerfd_create");
}
epollop->timerfd = -1;
}
} else {
epollop->timerfd = -1;
}
#endif
evsig_init_(base);
return (epollop);
}
//将最小堆中的最小超时时间设置给timerfd,epoll_wait阻塞直到timerfd有监听事件返回,返回后将
static int
epoll_dispatch(struct event_base *base, struct timeval *tv)
{
struct epollop *epollop = base->evbase;
struct epoll_event *events = epollop->events;
int i, res;
long timeout = -1;
#ifdef USING_TIMERFD
if (epollop->timerfd >= 0) {
struct itimerspec is;
is.it_interval.tv_sec = 0;
is.it_interval.tv_nsec = 0;
if (tv == NULL) { //没有超时事件,timerfd不会触发
/* No timeout; disarm the timer. */
is.it_value.tv_sec = 0;
is.it_value.tv_nsec = 0;
} else { //有超时事件
if (tv->tv_sec == 0 && tv->tv_usec == 0) {
/* we need to exit immediately; timerfd can't
* do that. */
timeout = 0; //超时时间为0,epoll_wait的超时值设置成0,立即返回
}
is.it_value.tv_sec = tv->tv_sec;
is.it_value.tv_nsec = tv->tv_usec * 1000; //设置timerfd触发的超时时间,epoll_wait的时间精度为微妙,timerfd的时间精度为纳秒
}
if (timerfd_settime(epollop->timerfd, 0, &is, NULL) < 0) { //给timerfd设置超时值,事件到达后epoll_wait会返回
event_warn("timerfd_settime");
}
} else
#endif
if (tv != NULL) { //不使用timerfd,使用epoll_wait超时
timeout = evutil_tv_to_msec_(tv);
if (timeout < 0 || 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_apply_changes(base);
event_changelist_remove_all_(&base->changelist, base);
EVBASE_RELEASE_LOCK(base, th_base_lock);
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); //监听timerfd和io fd事件
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (res == -1) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-1);
}
return (0);
}
for (i = 0; i < res; i++) {
int what = events[i].events;
short ev = 0;
#ifdef USING_TIMERFD
if (events[i].data.fd == epollop->timerfd) //timerfd触发的事件则循环处理下一个事件
continue;
#endif
...
evmap_io_active_(base, events[i].data.fd, ev | EV_ET); //将fd的事件加入到激活事件队列中
}
return (0);
}
使用timerfd的好处
在Linux系统中,epoll接口通常可以提供大约一毫秒级别的事件触发精度。因此,在默认情况下,使用CLOCK_MONOTONIC_COARSE时钟源作为基础进行定时操作是合理的,它既能保证时间的单调递增(不受系统时间调整的影响),又能满足大多数场景下的定时需求。
然而,对于一些需要更高定时精度的应用场景,libevent允许用户通过设置PRECISE_TIMER标志来提升事件循环(event_base)中的定时器精度。USING_TIMERFD宏被设置时,libevent会选择使用timerfd(定时器文件描述符)机制,因为它能够提供更细粒度的时间间隔控制,从而满足高精度定时任务的需求。同时timerfd将定时器事件转换成了Io事件,这样就对事件源进行了统一。
总结来说,libevent根据用户的实际需求动态选择定时器实现方式,以平衡性能与精确度,既确保了系统的高效运行,又能在必要时提供更为精确的定时服务。
二、处理Io事件
区别
libevent2.1版本新增 EV_CLOSED标记,当对端关闭连接后马上会触发该事件的回调函数进行处理;libevent2.0中通过触发EV_READ的回调函数,其中read返回0表示对端关闭了连接。
源码分析
libevent2.0版本
//将底层的epoll事件标记,转换成libevent的事件标记
if (what & (EPOLLHUP|EPOLLERR)) { //连接文件描述符被关闭或者出现错误
ev = EV_READ | EV_WRITE; //设置成读写事件
} else {
if (what & EPOLLIN)
ev |= EV_READ;
if (what & EPOLLOUT)
ev |= EV_WRITE;
}
libevent2.1版本增加了EV_CLOSED标记
if (what & EPOLLERR) { //文件描述符发生错误
ev = EV_READ | EV_WRITE;
} else if ((what & EPOLLHUP) && !(what & EPOLLRDHUP)) { //文件描述符被挂起排除对端关闭连接这种情况
ev = EV_READ | EV_WRITE;
} else {
if (what & EPOLLIN) //读事件
ev |= EV_READ;
if (what & EPOLLOUT) //写事件
ev |= EV_WRITE;
if (what & EPOLLRDHUP) //表示对端关闭了连接
ev |= EV_CLOSED;
}
总结
libevent源码还在不断更新当中,要用好最新的Libevent最好在官网中了解新增的特性。