libevent源码分析(二)

上一篇
上篇博文探讨了信号处理与libevent框架的结合,详细学习了struct event_base,struct event结构体及构成两个结构体的基本数据结构,见过了巧妙的函数回调机制与信号事件的唤醒机制,这对以后设计一个网络框架具有及其重要的意义。这篇文章介绍time-test.c这个例子,其通过libevent提供的定时事件来实现间隔固定时间打印的功能。

一、从time-test.c 示例代码开始

int lasttime;
static void timeout_cb(int fd, short event, void *arg){}
int main (int argc, char **argv)
{
	struct event timeout;
	struct timeval tv;
	/* Initalize the event library */
	event_init();
	/* Initalize one event */
	evtimer_set(&timeout, timeout_cb, &timeout);

	evutil_timerclear(&tv);
	tv.tv_sec = 2;
	event_add(&timeout, &tv);
	lasttime = time(NULL);
	event_dispatch();
	return (0);
}

此处先贴出struct timeval的结构体定义,而且我们发现此处未直接定义event_base,那意味着我们不使用吗,且看下面event_init()程序在言。

struct timeval {        //libevent-patches-1.4\compat\sys\_libevent_time.h
	long	tv_sec;		/* seconds */
	long	tv_usec;	/* and microseconds */
};

二、event_init函数为何物

struct event_base *current_base = NULL; //108line
struct event_base *event_init(void)     //163line
{//libevent-patches-1.4\event.c
	struct event_base *base = event_base_new();
	if (base != NULL)
		current_base = base;
	return (base);
}

一看便知原来其在本质上还是调用上篇已经剖析过的event_base_new()函数生成一个struct event_base对象,并且为全局变量current_base赋值,这样可以用于在各种场景下调用base结构,之后继续调用evtimer_set等函数用于初始化定时事件!

三、初始化定时事件

首先调用evtimer_set(&timeout, timeout_cb, &timeout);,然我们发现它是一个巧妙的宏,展开之后为event_set这个熟悉的函数,函数初始化timeout事件,并为其设置回调函数。

#define evtimer_set(ev, cb, arg)	event_set(ev, -1, 0, cb, arg)
void event_set(struct event *ev, int fd, short events,
	  void (*callback)(int, short, void *), void *arg)
{
	/* Take the current base - caller needs to set the real base later */
	ev->ev_base = current_base;

	ev->ev_callback = callback;
	ev->ev_arg = arg;
	ev->ev_fd = fd;
	ev->ev_events = events;
	ev->ev_res = 0;
	ev->ev_flags = EVLIST_INIT;
	ev->ev_ncalls = 0;
	ev->ev_pncalls = NULL;

	min_heap_elem_init(ev);

	/* by default, we put new events into the middle priority */
	if(current_base)
		ev->ev_pri = current_base->nactivequeues/2;
}

其上先重点关注timeout->ev_base = current_base,timeout->ev_callback = timeout_cb,timeout->ev_arg = &timeout这几个成员即可。
接着调用evutil_timerclear(&tv)这个宏,这个宏作用显而易见,清空struct timeval tv值。 最后函数会设置时间间隔为2s.
#define evutil_timerclear(tvp) timerclear(tvp)
#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0
接着就要调用event_add函数将该定时事件加入到监控中去。

四、瞧一瞧event_add如何添加定时事件

int event_add(struct event *ev, const struct timeval *tv)
{
    ...
	if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
		if (min_heap_reserve(&base->timeheap,1 + min_heap_size(&base->timeheap)) == -1) 
		//此处主要用于为current_base->timeheap设置相应的值
			return (-1);  /* ENOMEM == errno */
	}
    .../*省略部分为将事件加入到poll/epoll监听机制里面的函数逻辑,显然定时器不需要使用他们*/
    if (res != -1 && tv != NULL) {
		struct timeval now;
        ...
		gettime(base, &now);//获取现在时间
		evutil_timeradd(&now, tv, &ev->ev_timeout); //设置定时事件的超时时间
        。。。
		event_queue_insert(base, ev, EVLIST_TIMEOUT);
	}

	return (res);
}

故上面的函数首先调用min_heap_reserve用于为current_base->timeheap设置相应的值,然后设置定时事件的超时时间即ev->ev_timeout值。最后调用event_queue_insert(base, ev, EVLIST_TIMEOUT)函数,其主要做了些下面的事,其中min_heap_push函数则是将该要监听的超时事件添加到base->timeheap这个小根堆上以便后面取出该事件使用!

void event_queue_insert(struct event_base *base, struct event *ev, int queue)
{   ...
	if (~ev->ev_flags & EVLIST_INTERNAL)
		base->event_count++;
	ev->ev_flags |= queue;
    ...
	case EVLIST_TIMEOUT: {
		min_heap_push(&base->timeheap, ev);
		break;
}

五、最后调用event_dispatch让定时事件跑起来

event_dispatch()调用event_loop(0),其继续调用event_base_loop(current_base, 0),在event_base_loop中最重要的就是一个while循环我们来看一看在定时事件上其如何发挥作用。

	done = 0;
	while (!done) {
		/* Terminate the loop if we have been asked to */
        ......
		timeout_correct(base, &tv);
        /*简而言之,此处当为更新超时时间*/
		tv_p = &tv;
		if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
			timeout_next(base, &tv_p);
		} else {
			evutil_timerclear(&tv);}
	    ...	
    	/* update last old time */
		gettime(base, &base->event_tv);

		/* clear time cache */
		base->tv_cache.tv_sec = 0;
        /*对于超时事件来说,dispatch不做任何事,超时后立即返回*/
		res = evsel->dispatch(base, evbase, tv_p);
		gettime(base, &base->tv_cache);
        /*timeout_process若有超时事件发生则处理之*/
		timeout_process(base);

		if (base->event_count_active) {
			event_process_active(base);//由该函数处理超时事件
			if (!base->event_count_active && (flags & EVLOOP_ONCE))
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK)
			done = 1;
	}

显然由上面的代码可知,若有超时事件发生时首先调用timeout_process(base),

void
timeout_process(struct event_base *base)
{
    ...
	while ((ev = min_heap_top(&base->timeheap))) {
		if (evutil_timercmp(&ev->ev_timeout, &now, >))
			break;
		/* delete this event from the I/O queues */
		event_del(ev);
        ...
		event_active(ev, EV_TIMEOUT, 1);
	}
}

该函数首先判断确实有超时事件发生,然后调用event_active(ev, EV_TIMEOUT, 1)函数处理该超时事件ev,把该函数外壳拔掉则为event_queue_insert(ev->ev_base, ev, EVLIST_ACTIVE);函数

void event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
    。。。
	if (~ev->ev_flags & EVLIST_INTERNAL)
		base->event_count++;
    。。。
	case EVLIST_ACTIVE:
		base->event_count_active++;
		TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri],
		    ev,ev_active_next);
		break;
    。。。
}

面对超时事件他会调用TAILQ_INSERT_TAIL宏将该事件加入到base->activequeues[0]这个待处理队列中去,至此终于将超时事件处理完毕加入等待队列,接下来继续回到base->activequeues中去执行下一个函数去处理它。即执行event_process_active(base)函数,这是一个熟悉的函数,在上一章节中刚讲过它的具体处理逻辑,现在把他再贴出来:

static void
event_process_active(struct event_base *base)
{
    .../*获取处理事件头*/
	for (i = 0; i < base->nactivequeues; ++i) {
		if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
			activeq = base->activequeues[i];
			break;
		}
	}
    /*for循环内依次处理每一个待处理事件*/
	for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
		if (ev->ev_events & EV_PERSIST)
			event_queue_remove(base, ev, EVLIST_ACTIVE);
		else
			event_del(ev);
		/* Allows deletes to work */
		ncalls = ev->ev_ncalls;
		ev->ev_pncalls = &ncalls;
		while (ncalls) {
			ncalls--;
			ev->ev_ncalls = ncalls;
			(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
			if (event_gotsig || base->event_break)
				return;
		}
	}
}

最终会调用触发事件的回调函数,本事件应该出发头部的timeout_cb函数。

六、总结

总之,通过两篇文章两个例子熟悉了libevent的基本执行流程,从事件源的构造与初始化、事件多路分发机制的设计、Reactor框架的设计及事件处理程序等多个方面为我们展示了高性能网络框架的奥秘与设计思路。印象最深的莫过于双向链表的使用与维护,完备的事件侦测与校验机制以及可怕的事件回调机制方式,妙!
下一篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
luotuo44是一个GitHub上的开项目,其主要目的是分析libevent码。libevent是一个使用C语言编写的事件驱动库,可以用于开发高性能的网络服务器和客户端应用程序。 在分析libevent码时,luotuo44首先研究了libevent的基本结构和使用方法。他深入研究了libevent的事件循环机制、事件触发方式以及事件回调函数的实现原理。通过仔细阅读代码,他详细解释了libevent是如何利用系统底层的IO多路复用技术(如select、epoll等)来实现高效的事件处理。 此外,luotuo44还分析libevent的缓冲区管理和事件优先级处理机制。他对libevent的缓冲区数据结构和操作进行了详细解读,包括如何实现缓冲区的读写以及缓冲区事件的触发和处理。他还深入探讨了libevent的事件优先级机制,介绍了如何设置和管理不同优先级的事件,并解释了事件优先级对事件处理效率的影响。 除了基本功能外,luotuo44还分析libevent的线程安全性和性能优化策略。他详细讲解了libevent的线程安全机制,包括互斥锁、条件变量等,并提供了一些最佳实践指南,以确保多线程环境下的稳定性和性能。此外,他还分享了一些他自己的性能优化经验,包括使用合适的数据结构、避免频繁内存分配与释放等。 总的来说,luotuo44的libevent分析为那些想深入了解libevent内部原理和如何正确使用libevent的开发者提供了很大的帮助。通过他的分析,读者可以更好地理解libevent的工作原理,并从中学习到一些编程技巧和优化策略。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值