[libevent]event,event_base结构体描述

event

libevent的核心-event

Libevent是基于事件驱动(event-driven)的,从名字也可以看到event是整个库的核心event就是Reactor框架中的事件处理程序组件;它提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄。

//在event2/event_struct.h中event的结构描述
struct event {
	TAILQ_ENTRY (event) ev_next;            /*增加下一个事件*/
	TAILQ_ENTRY (event) ev_active_next;     /*增加下一个活动事件*/
	TAILQ_ENTRY (event) ev_signal_next;     /*增加下一个信号*/
	/*ev_next,ev_active_next和ev_signal_next都是双向链表节点指针;它们是libevent对不同事件类型和在不同的时期,对事件的管理时使用到的字段。
	libevent使用双向链表保存所有注册的I/O和Signal事件,ev_next就是该I/O事件在链表中的位置;称此链表为“已注册事件链表”;
	同样ev_signal_next就是signal事件在signal事件链表中的位置;
	ev_active_next:libevent将所有的激活事件放入到链表active list中,然后遍历active list执行调度,ev_active_next就指明了event在active list中的位置;*/

	unsigned int min_heap_idx;         /* for managing timeouts 表示该event保存在min_heap数组中的索引*/ 
	struct timeval ev_timeout;   //用来保存事件的超时时间
	/*min_heap_idx和ev_timeout,如果是timeout事件,它们是event在小根堆中的索引和超时值,libevent使用小根堆来管理定时事件*/

	struct event_base *ev_base;   /* 该事件所属的反应堆实例,这是一个event_base结构体*/

	int ev_fd;               /*对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号*/
	short ev_events;        /*event关注的事件类型,它可以是以下3种类型:
							I/O事件: EV_WRITE和EV_READ
							定时事件:EV_TIMEOUT
							信号:    EV_SIGNAL
							辅助选项:EV_PERSIST,表明是一个永久事件
							Libevent中的定义为:
							#define EV_TIMEOUT 0x01   
							#define EV_READ  0x02   
							#define EV_WRITE 0x04   
							#define EV_SIGNAL 0x08   
							#define EV_PERSIST 0x10 
							可以看出事件类型可以使用“|”运算符进行组合,需要说明的是,信号和I/O事件不能同时设置;
							还可以看出libevent使用event结构体将这3种事件的处理统一起来;*/ 
	void *ev_arg; /*回调函数的参数,void*,表明可以是任意类型的数据,在设置event时指定*/
	void (*ev_callback)(int fd, short events, void *arg);   
	/*event的回调函数,被ev_base调用,执行事件处理程序,这是一个函数指针,
	其中参数fd对应于ev_fd;events对应于ev_events;arg对应于ev_arg*/

	int ev_pri;  /* smaller numbers are higher priority */

	short ev_ncalls;        /*事件就绪执行时,调用ev_callback的次数,通常为1*/ 
	short *ev_pncalls; /* Allows deletes in callback ,指针,通常指向ev_ncalls或者为NULL*/
	int ev_res;  /* result passed to event callback ,记录了当前激活事件的类型*/
	int ev_flags; /*libevent用于标记event信息的字段,表明其当前的状态,可能的值有:
				  #define EVLIST_TIMEOUT 0x01 // event在time堆中   
				  #define EVLIST_INSERTED 0x02 // event在已注册事件链表中   
				  #define EVLIST_SIGNAL 0x04 // 未见使用   
				  #define EVLIST_ACTIVE 0x08 // event在激活链表中   
				  #define EVLIST_INTERNAL 0x10 // 内部使用标记   
				  #define EVLIST_INIT     0x80 // event已被初始化  
				  #define EVLIST_TIMEOUT 0x01 // event在time堆中
				  #define EVLIST_INSERTED 0x02 // event在已注册事件链表中
				  #define EVLIST_SIGNAL 0x04 // 未见使用
				  #define EVLIST_ACTIVE 0x08 // event在激活链表中
				  #define EVLIST_INTERNAL 0x10 // 内部使用标记
				  #define EVLIST_INIT     0x80 // event已被初始化 */          
};

libeventevent的管理

event结构体中的3个链表节点指针和一个堆索引出发,大体上也能窥出libeventevent的管理方法了,可以参见下面的示意图:

  

每次当有事件event转变为就绪状态时,libevent就会把它移入到active event list[priority],其中priorityevent的优先级;接着libevent会根据自己的调度策略选择就绪事件,调用其cb_callback()函数执行事件处理;并根据就绪的句柄和事件类型填充cb_callback函数的参数。 

event事件设置的接口函数

要向libevent添加一个事件,需要首先设置event对象,这通过调用libevent提供的函数有:event_set(), event_base_set(), event_priority_set()来完成; 

void event_set(struct event *ev, int fd, short events,void (*callback)(int, short, void *), void *arg)

  • 1.设置事件ev绑定的文件描述符或者信号,对于定时事件,设为-1即可;
  • 2.设置事件类型,比如EV_READ|EV_PERSIST, EV_WRITE, EV_SIGNAL等;
  • 3.设置事件的回调函数以及参数arg
  • 4.初始化其它字段,比如缺省的event_base和优先级;

int event_base_set(struct event_base *base, struct event *ev)

  • 设置event ev将要注册到的event_base
  • libevent有一个全局event_base指针current_base,默认情况下事件ev将被注册到current_base上,使用该函数可以指定不同的event_base
  • 如果一个进程中存在多个libevent实例,则必须要调用该函数为event设置不同的event_base 

int event_priority_set(struct event *ev, int pri)

  • 设置event ev的优先级,没什么可说的,注意的一点就是:当ev正处于就绪状态时,不能设置,返回-1

event_base

事件处理框架-event_base

//event_base 定义在event-internal.h文件中
struct event_base {
	const struct eventop *evsel;      //表示选择的事件引擎,可能为:epoll, poll, select
	void *evbase;                     //全局对象
	/* evsel和evbase这两个字段的设置可能会让人有些迷惑,这里你可以把evsel和evbase看作是类和静态函数的关系,
	比如添加事件时的调用行为:evsel->add(evbase, ev),实际执行操作的是evbase;这相当于class::add(instance, ev),instance就是class的一个对象实例。
	evsel指向了全局变量static const struct eventop *eventops[]中的一个;
	前面也说过,libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。
	evbase实际上是一个eventop实例对象;先来看看eventop结构体,它的成员是一系列的函数指针, 在event-internal.h文件中:
	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 *); // 注销,释放资源 
	int need_reinit;// set if we need to reinitialize the event base 
	};
	也就是说,在libevent中,每种I/O demultiplex机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。
	比如对于epoll,libevent实现了5个对应的接口函数,并在初始化时并将eventop的5个函数指针指向这5个函数,那么程序就可以使用epoll作为I/O demultiplex机制了 */

	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;
	int nactivequeues;
	/*是一个二级指针,前面讲过libevent支持事件优先级,因此你可以把它看作是数组,
	其中的元素activequeues[priority]是一个链表, 链表的每个节点指向一个优先级为priority的就绪事件event。*/

	struct evsignal_info sig; /* signal handling info,是来管理信号的结构体 */
	struct event_list eventqueue;//链表,保存了所有的注册事件event的指针。
	struct min_heap timeheap;  //用来检测事件是否超时的堆栈,是管理定时事件的小根堆
	struct timeval event_tv;        //系统的当前时间
	struct timeval tv_cache;   //与event::ev_timeout进行比较,确定事件是否超时
	//event_tv和tv_cache是libevent用于时间管理的变量
};

创建和初始化event_base

创建一个event_base对象也既是创建了一个新的libevent实例,程序需要通过调用event_init()(内部调用event_base_new()函数执行具体操作)函数来创建,该函数同时还对新生成的libevent实例进行了初始化。

该函数首先为event_base实例申请空间;然后初始化timer mini-heap;选择并初始化合适的系统I/O demultiplexer机制;初始化各事件链表;函数还检测了系统的时间设置,为后面的时间管理打下基础。

接口函数

前面提到Reactor框架的作用就是提供事件的注册、注销接口;根据系统提供的事件多路分发机制执行事件循环当有事件进入“就绪”状态时,调用注册事件的回调函数来处理事件Libevent中对应的接口函数主要就是:

  • int  event_add(struct event *ev, const struct timeval *timeout);
  • int  event_del(struct event *ev);
  • int  event_base_loop(struct event_base *base, int loops);
  • void event_active(struct event *event, int res, short events);
  • void event_process_active(struct event_base *base); 

下面将按介绍事件注册和删除的代码流程,libevent的事件循环框架将在下一文中再具体描述。

对于定时事件,这些函数将调用timer heap管理接口执行插入和删除操作;对于I/OSignal事件将调用eventop的adddelete接口函数执行插入和删除操作(eventop会对Signal事件调用Signal处理接口执行操作);

注册事件

函数原型:int event_add(struct event *ev, const struct timeval *tv) 参数:ev:指向要注册的事件; tv:超时时间; 

函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明,如果注册成功,ev将被插入到已注册链表中;如果tv不是NULL,则会同时注册定时事件,将ev添加到timer堆上;

如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。这个函数也体现了libevent细节之处的巧妙设计,且仔细看程序代码,部分有省略,注释直接附在代码中。 

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策略
	// 新的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 */
	}
	// 如果事件ev不在已注册或者激活链表中,则调用evbase注册事件
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
		!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
			res = evsel->add(evbase, ev);
			if (res != -1) // 注册成功,插入event到已注册链表中
				event_queue_insert(base, ev, EVLIST_INSERTED);
	}
	// 准备添加定时事件
	if (res != -1 && tv != NULL) {
		struct timeval now;
		// EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的
		if (ev->ev_flags & EVLIST_TIMEOUT)
			event_queue_remove(base, ev, EVLIST_TIMEOUT);
		// 如果事件已经是就绪状态则从激活链表中删除
		if ((ev->ev_flags & EVLIST_ACTIVE) &&
			(ev->ev_res & EV_TIMEOUT)) {
				// 将ev_callback调用次数设置为0
				if (ev->ev_ncalls && ev->ev_pncalls) {
					*ev->ev_pncalls = 0;
				}
				event_queue_remove(base, ev, EVLIST_ACTIVE);
		}
		// 计算时间,并插入到timer小根堆中
		gettime(base, &now);
		evutil_timeradd(&now, tv, &ev->ev_timeout);
		event_queue_insert(base, ev, EVLIST_TIMEOUT);
	}
	return (res);
}
void event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
	// ev可能已经在激活列表中了,避免重复插入
	if (ev->ev_flags & queue) {
		if (queue & EVLIST_ACTIVE)
			return;
	}
	// ...
	ev->ev_flags |= queue; // 记录queue标记
	switch (queue) {
	case EVLIST_INSERTED: // I/O或Signal事件,加入已注册事件链表
		TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
		break;
	case EVLIST_ACTIVE: // 就绪事件,加入激活链表
		base->event_count_active++;
		TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);
		break;
	case EVLIST_TIMEOUT: // 定时事件,加入堆
		min_heap_push(&base->timeheap, ev);
		break;
	}
} 

删除事件 

函数原型为:int event_del(struct event *ev); 

该函数将删除事件ev,对于I/O事件,从I/O 的demultiplexer上将事件注销;对于Signal事件,将从Signal事件链表中删除;对于定时事件,将从堆上删除; 

同样删除事件的操作则不一定是原子的,比如删除时间事件之后,有可能从系统I/O机制中注销会失败。 

int event_del(struct event *ev)
{
	struct event_base *base;
	const struct eventop *evsel;
	void *evbase;
	// ev_base为NULL,表明ev没有被注册
	if (ev->ev_base == NULL)
		return (-1);
	// 取得ev注册的event_base和eventop指针
	base = ev->ev_base;
	evsel = base->evsel;
	evbase = base->evbase;
	// 将ev_callback调用次数设置为
	if (ev->ev_ncalls && ev->ev_pncalls) {
		*ev->ev_pncalls = 0;
	}
	// 从对应的链表中删除
	if (ev->ev_flags & EVLIST_TIMEOUT)
		event_queue_remove(base, ev, EVLIST_TIMEOUT);
	if (ev->ev_flags & EVLIST_ACTIVE)
		event_queue_remove(base, ev, EVLIST_ACTIVE);
	if (ev->ev_flags & EVLIST_INSERTED) {
		event_queue_remove(base, ev, EVLIST_INSERTED);
		// EVLIST_INSERTED表明是I/O或者Signal事件,
		// 需要调用I/O demultiplexer注销事件
		return (evsel->del(evbase, ev));
	}
	return (0);
}

分析了event_base这一重要结构体,初步看到了libevent对系统的I/O demultiplex机制的封装event_op结构,并结合源代码分析了事件的注册和删除处理,下文中将会接着分析事件管理框架中的主事件循环部分。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页