libevent的event处理框架剖析

前几次剖析了libevent的tail queue和evbuffer,今天来剖析一下它的事件处理框架。这个在剖析evbuffer之前已经大致走过几遍,但思路不是很清晰,是因为我没有用实例去测试event流程。通过这次我学习到了剖析源码不仅要去看源码,而且你要测试它这项接口是怎么用的,不然只会似懂非懂。

首先来看一下event结构体:

struct event {
	TAILQ_ENTRY (event) ev_next;  //已注册事件链表,名字叫ev_next,event是类型,非活跃, TAILQ_ENTRY在我之前的博客已经剖析过了,不再赘述
	TAILQ_ENTRY (event) ev_active_next;   //活跃事件链表,叫链表或者队列都行,下文碰见队列也是说这几个
	TAILQ_ENTRY (event) ev_signal_next;   //信号事件链表
	unsigned int min_heap_idx;	/* for managing timeouts */ //超时事件在小根堆中的索引
										
	struct event_base *ev_base;  //该事件所属的反应堆实例,它指向一个event_base反应堆

	int ev_fd;                //对于IO事件是文件描述符,对于信号事件,是绑定的信号
	short ev_events;   //关注的事件类型,如EV_WRITE,EV_READ,EV_TIMEOUT,EV_SIGNAL,EV_PRESIST,可以使用|组合
	short ev_ncalls;      //事件就绪时,调用ev_callback执行的次数,通常为1
	short *ev_pncalls;	/* Allows deletes in callback */    //通常指向event_ncalls,或者为NULL

	struct timeval ev_timeout;    //超时事件的超时值

	int ev_pri;		/* smaller numbers are higher priority */   //优先级

	void (*ev_callback)(int, short, void *arg);   //event回调函数,执行事件处理程序,fd对应ev_fd,events对应
									        //ev_events, arg对应ev_arg
	void *ev_arg;         //设置event时指定                         

	int ev_res;		/* result passed to event callback */  //记录了当前激活事件的类型
	int ev_flags;     //标记event信息的字段,表明当前状态,如EVLIST_TIMEOUT, EVLIST_INSERTED, EVLIST_ACTIVE等。
};
下面是event_base结构体:

struct event_base {
	const struct eventop *evsel;  //用来接收系统编译前设置I/O机制的名字,来自eventops
	void *evbase;     //接收init返回的I/O多路复用的op,如epollop
	int event_count;		/* counts number of total events */  //事件总数
	int event_count_active;	/* counts number of active events */   //活跃事件数目

	int event_gotterm;		/* Set to terminate loop */  
	int event_break;		/* Set to terminate loop immediately */

	/* active event management */
	struct event_list **activequeues; //activequeues[priority]是一个链表,链表中的每个结点都是优先级为
	 							  //priority的就绪事件event
	int nactivequeues;          //活跃链表的数目

	/* signal handling info */
	struct evsignal_info sig;      //专门管理信号的结构体

	struct event_list eventqueue;   //链表,保存了所有注册事件event的指针
	struct timeval event_tv;       //管理时间变量

	struct min_heap timeheap;   //用来管理定时时间的小根堆

	struct timeval tv_cache;   //管理时间变量
};
event_base中的evsel的类型是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;    //设置是否重新初始化
};

eventop即event operator,我们可以看到它的内部封装了事件操作的名字,以及各种方法,可以用来配合实现C语言的多态。这里实际上将会封装I/O多路复用选项,比如:name=epoll,然后init,add等方法就用epoll的方法,或者name=select,用select的方法。至于怎么选,看下面。

evsel实际上会在event_base_new函数里面初始化,会根据你在编译前的选项来选择I/O multiplexer,我把ecent_base_new函数中选择部分截取出来如下,以及eventops选项数组。

        //根据系统配置和编译选项决定使用哪一种I/O demultiplex机制
	//可以看出,libevent在编译阶段选择系统的I/O demultiplex机制,而不支持在运行阶段根据配置再次选择
	base->evbase = NULL;
	for (i = 0; eventops[i] && !base->evbase; i++) {  //按顺序遍历,因为这些机制是按性能由大到小排的
		base->evsel = eventops[i];

		base->evbase = base->evsel->init(base);
	}
eventops选项数组如下,linux支持epoll,poll,select,按性能排的序。

//所有支持的I/O demultiplex机制存储在这此全局静数组中,在编译阶段选择使用何种机制,数组内容根据优先级顺序声明如下
/* 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
};


经过上面对几个关键结构体的分析,我们再来看一下这几个结构体的关系图(图非原创,出处忘了):

libevent的event处理框架剖析_第1张图片
我们注册一个事件event,这个事件内部有一个指针,指向了一个反应堆event_base。event_base中有已注册事件队列,活跃事件队列,还有定时事件的最小堆。当我们调用event会根据该事件类型选择注册到哪个队列或者min_heap中,并且event_base还有反应堆的一系列属性,如时间缓存,总事件计数器,以及I/O multiplexer的方法。

流程图大概是下面这样:
libevent的event处理框架剖析_第2张图片

接下来我们来按照流程图剖析一下整体流程。


整个流程用一段socket代码来举例就是下面这样子,仅展示主函数,下文会有完整示例。

int main()
{
    int sockfd = socket_packaging(SERVER_IP, SERVER_PORT);
    
    base = event_base_new();

    struct event listen_ev;
    event_set(&listen_ev, sockfd, EV_READ|EV_PERSIST, on_accept, NULL);
    event_base_set(base, &listen_ev);
    event_add(&listen_ev, NULL);
    
    event_base_dispatch(base);
    
    return 0;
}

然后我们来剖析库里面的具体实现。


首先调用event_init()函数,调用这函数前我们要知道该文件中有一个全局变量:

struct event_base *current_base = NULL;
在init函数中会初始化,它 执行完该函数会初始化一个反应堆实例。

struct event_base * 
event_init(void)
{
	struct event_base *base = event_base_new();

	if (base != NULL)
		current_base = base;    //初始化的时候在这里把当前event_base赋值为new出来的值,以后全局共享这个反应堆

	return (base);
}

然后用户端会调用event_set()函数,该函数会设置我们要产生的事件event。

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;   //init函数已经赋值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;   //标记event信息的字段,表明当前状态,如EVLIST_TIMEOUT, EVLIST_INSERTED,
	ev->ev_ncalls = 0;     //时间就绪时回调函数执行的次数
	ev->ev_pncalls = NULL;   //指向ncalls

	min_heap_elem_init(ev);   //最小堆中下标初始为-1

	/* by default, we put new events into the middle priority */
	if(current_base)
		ev->ev_pri = current_base->nactivequeues/2;   //默认将优先级设为活跃链表中间值,活跃链表的下标就是优先级
}
event函数相当于初始化event,比如设置事件发生时要执行的回调函数,但是我们是要将event加入反应堆相应序列的,于是先调用下面的event_base_set()函数,也就是将它们关联起来。(我之前有疑惑,为什么不把event和event_base关联的动作交给库来执行?实际上这是客端的责任,如果一个程序中有多个event_base实例,event可以被关联进不同的反应堆中)。

int
event_base_set(struct event_base *base, struct event *ev)
{
	/* Only innocent events may be assigned to a different base */
	if (ev->ev_flags != EVLIST_INIT)     //只有无辜的事件可以分配给一个不同的base
		return (-1);

	ev->ev_base = base;
	ev->ev_pri = base->nactivequeues/2;  //优先级一半

	return (0);
}
关联起来之后,接下来应该把event真正的放入event_base相应的事件队列中,调用event_add()函数,但是event_add()函数不仅在这种情况可以用,有可能某个事件已经被add过了,但是又想给它设置定时,或者换个定时时间,将其加入或者重新加入定时min_heap中,也要调用这个函数。

int
event_add(struct event *ev, const struct timeval *tv)
{
	struct event_base *base = ev->ev_base;  //要注册到的event_base
	const struct eventop *evsel = base->evsel;
	void *evbase = base->evbase;     //base使用的系统I/O策略
	int res = 0;

	event_debug((
		 "event_add: event: %p, %s%s%scall %p",
		 ev,
		 ev->ev_events & EV_READ ? "EV_READ " : " ",
		 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
		 tv ? "EV_TIMEOUT " : " ",
		 ev->ev_callback));

	assert(!(ev->ev_flags & ~EVLIST_ALL)); //断言确保有标志位

	/*
	 * prepare for timeout insertion further below, if we get a
	 * failure on any step, we should not change any state.
	 */
	//如果tv不是NULL,同时注册定时事件,否则将事件插入到已插入链表
	 //新的timer事件,调用timer heap接口在堆上预留一个位置
	 //这样能保证操作的原子性
	 //向系统I/O机制注册可能会失败,而当在堆上预留成功后   // ??
	 //定时事件的添加将肯定不会失败
	 //而预留位置的可能结果是堆扩充,但是内部元素并不会改变
	if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
		if (min_heap_reserve(&base->timeheap,
			1 + min_heap_size(&base->timeheap)) == -1)
			return (-1);  /* ENOMEM == errno */
	}

	//如果事件不在已注册或者激活链表中,则调用evbase注册事件。如果在并且tv!=NULL,仅添加定时事件
	//这一步就是前面所说的可能失败
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
		res = evsel->add(evbase, ev);                //加上相应I/O操作,比如epoll_add,上文已经分析过了
		if (res != -1)           //注册成功,插入event到已注册链表中
			event_queue_insert(base, ev, EVLIST_INSERTED);
	}
  
	/* 
	 * we should change the timout state only if the previous event
	 * addition succeeded.
	 */
	 //准备添加定时事件
	if (res != -1 && tv != NULL) {
		struct timeval now;

		/* 
		 * we already reserved memory above for the case where we
		 * are not replacing an exisiting timeout.
		 */
		 //EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的!   
		if (ev->ev_flags & EVLIST_TIMEOUT)
			event_queue_remove(base, ev, EVLIST_TIMEOUT);   //删除旧的,换新的定时

		/* Check if it is active due to a timeout.  Rescheduling
		 * this timeout before the callback can be executed
		 * removes it from the active list. */
		 //如果事件已经是就绪状态则从激活链表中删除,因为就绪状态不能修改     //这就是该事件可能已经add过了,而且活跃,活跃不能改,先删除再改
		if ((ev->ev_flags & EVLIST_ACTIVE) &&
		    (ev->ev_res & EV_TIMEOUT)) {    //???
			/* See if we are just active executing this
			 * event in a loop
			 */
			 //将ev_callback调用的次数设置为0         ???
			if (ev->ev_ncalls && ev->ev_pncalls) {
				/* Abort loop */
				*ev->ev_pncalls = 0;
			}
			
			event_queue_remove(base, ev, EVLIST_ACTIVE);  //从对应的链表中删除
		}

		//计算时间,并插入到timer小根堆中
		gettime(base, &now);
		evutil_timeradd(&now, tv, &ev->ev_timeout);  //该函数下次分析

		event_debug((
			 "event_add: timeout in %ld seconds, call %p",
			 tv->tv_sec, ev->ev_callback));

		event_queue_insert(base, ev, EVLIST_TIMEOUT);    //将事件插入到对应链表中,该函数内部分情况插入队列或者堆中,本次暂不分析。
	}

	return (res);
}
在上面这个函数中,我们将event插入到反应堆相应位置,并选择了相应的I/O multiplexer,事件发生时要执行的回调函数我们已经在event_set()函数中设置过了,现在什么都不用干了,就来一个大循环,等待事件发生,执行回调函数的操作就行了。循环由event_base_loop()函数负责:

int
event_base_loop(struct event_base *base, int flags)
{
	const struct eventop *evsel = base->evsel;
	void *evbase = base->evbase;
	struct timeval tv;
	struct timeval *tv_p;
	int res, done;

	//清空时间缓存
	/* clear time cache */
	base->tv_cache.tv_sec = 0;

	//evsignal_base是全局变量,在处理signal时,用于指明signal所属的event_base实例
	if (base->sig.ev_signal_added)     
		evsignal_base = base;   
	done = 0;
	while (!done) {
		//查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记
		//调用event_base_loopbreak()设置event_break标记
		/* Terminate the loop if we have been asked to */
		if (base->event_gotterm) {
			base->event_gotterm = 0;
			break;
		}

		if (base->event_break) {
			base->event_break = 0;
			break;
		}

		//校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间
		//在timeout_correcct函数里,比较last wait time和当前时间,如果当前时间小于last wait time
		//表明时间有问题,这时需要更新timer_heap中所有定时事件的超时时间
		timeout_correct(base, &tv);

		//根据timer heap中的最小超时时间,计算系统I/O demultiplexer的
		tv_p = &tv;
		if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {  //???
			timeout_next(base, &tv_p);
		} else {
			//根据Timer时间计算evsel-dispatch的最大等待时间
			/* 
			 * if we have active events, we just poll new events
			 * without waiting.
			 */
			evutil_timerclear(&tv);
		}

		//如果当前没有注册事件,就退出
		/* If we have no events, we just exit */
		if (!event_haveevents(base)) {
			event_debug(("%s: no events registered.", __func__));
			return (1);
		}

		//更新last wait time
		/* update last old time */
		gettime(base, &base->event_tv);

		//清空time cache
		/* clear time cache */
		base->tv_cache.tv_sec = 0;

		//调用系统I/O demultplexer 等待就绪I/O事件,可能是epoll_wait或者select等
		//在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中
		res = evsel->dispatch(base, evbase, tv_p);

		if (res == -1)
			return (-1);
		//将time cache赋值为当前系统时间
		gettime(base, &base->tv_cache);

		//检查heap中的timer events,将就绪的timer event从heap上删除,并插入激活链表中
		timeout_process(base);

		//调用event_process_active()处理激活链表中就绪的event,调用其回调函数执行事件处理
		//该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表
		//然后处理链表中的所有就绪事件
		//因此低优先级的就绪事件可能得不到及时处理
		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;
	}

	/* clear time cache */
	base->tv_cache.tv_sec = 0;   //循环结束,清缓存

	event_debug(("%s: asked to terminate loop.", __func__));
	return (0);
}

loop函数通过cache来缓存时间,这就省去了每次循环都要先systemcall,提高了效率。

整个流程就是上面这个样子,某些细节函数可能暂时没有分析,后续会给上详细剖析。


下面有一个echoserver的例子,就是上面那个主函数所属程序的完成代码,可以参考帮助理解event handling process。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SERVER_IP   "127.0.0.1"
#define SERVER_PORT 6666
#define BACKLOG     5
#define MAX_SIZE    1024

struct event_base *base = NULL;

struct sock_ev{
    struct event* read_ev;
    struct event* write_ev;
    char   *buffer;
};

int socket_packaging(const char* ip, const unsigned int port);
void on_write(int sockfd, short event, void* arg);
void on_accept(int sock, short event, void *arg);

int main()
{
    int sockfd = socket_packaging(SERVER_IP, SERVER_PORT);

    base = event_base_new();

    struct event listen_ev;
    event_set(&listen_ev, sockfd, EV_READ|EV_PERSIST, on_accept, NULL);
    event_base_set(base, &listen_ev);
    event_add(&listen_ev, NULL);

    event_base_dispatch(base);

    return 0;
}

void release_sock_event(struct sock_ev* ev)
{
    event_del(ev->read_ev);
    free(ev->read_ev);
    free(ev->write_ev);
    free(ev->buffer);
    free(ev);
}

void on_write(int sockfd, short event, void* arg)
{
    char *buffer = (char *)arg;
    send(sockfd, buffer, strlen(buffer)+1, 0);
    free(buffer);
}

void on_read(int sockfd, short event, void* arg)
{
    struct event* write_ev;
    int size;
    struct sock_ev* ev = (struct sock_ev*)arg;
    ev->buffer = (char *)malloc(MAX_SIZE);
    memset(ev->buffer, 0, sizeof(ev->buffer));
    size = recv(sockfd, ev->buffer, MAX_SIZE, 0);
    printf("receive data:%s, size:%d\n", ev->buffer, size);

    if(size == 0){
        release_sock_event(ev);
        close(sockfd);
        return ;
    }

    event_set(ev->write_ev, sockfd, EV_WRITE, on_write, ev->buffer);
    event_base_set(base, ev->write_ev);
    event_add(ev->write_ev, NULL);
}

void on_accept(int sock, short event, void *arg)
{
    struct sockaddr_in cli_addr;
    int newfd;
    struct sock_ev* ev = (struct sock_ev*)malloc(sizeof(struct sock_ev));

    ev->read_ev = (struct event*)malloc(sizeof(struct event));
    ev->write_ev = (struct event*)malloc(sizeof(struct event));

    socklen_t sin_size = sizeof(struct sockaddr_in);
    newfd = accept(sock, (struct sockaddr *)&cli_addr, &sin_size);

    event_set(ev->read_ev, newfd, EV_READ|EV_PERSIST, on_read, ev);
    event_base_set(base, ev->read_ev);
    event_add(ev->read_ev, NULL);
}

int socket_packaging(const char* ip, const unsigned int port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(sockfd != -1);

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(ip);

    int on = 1;
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    assert(ret != -1);

    ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    assert(ret != -1);

    ret = listen(sockfd, BACKLOG);
    assert(ret != -1);

    return sockfd;
}
客户端的程序就不写了,相信每个人都可以轻松写出来。

本篇博客还有好多细节没有剖析,后续博客会更加完善。

(FreeeLinux的博客:http://blog.csdn.net/freeelinux/article/details/52812857)

你可能感兴趣的:(Libevent源码剖析,libevent源码剖析)