libevent2.0 和 libevent2.1的区别


前言

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最好在官网中了解新增的特性。

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值