事件处理框架

一、事件处理框架-event_base

Reactor模式有几个基础的组件,其中的Reactor框架就是event_base结构体,下面是定义(只列出部分常用的成员),如下:

struct event_base {
	/* 后台使用的I/O多路复用机制 */
	const struct eventop *evsel;
	/* 指向struct epollop对象的指针 */
	void *evbase;
	/**
	 * 激活队列(数组)用来保存激活事件对应的回调函数。每个事件有一个event_callback类型的成员ev_evcallback,用来保存事件对应的回调
	 * 当事件被触发时,会把事件的回调保存到激活队列。激活队列是一个优先级队列,下标越小优先级越高。
	 */
	struct evcallback_list *activequeues;
	/* activequeues 数组的长度 */
	int nactivequeues;
	
	/* 公共超时队列 */
	struct common_timeout_list **common_timeout_queues;
	/* 公共超时队列的实际长度 */
	int n_common_timeouts;
	/* 公共超时队列的容量 */
	int n_common_timeouts_allocated;

	/* 保存注册的I/O事件 */
	struct event_io_map io;
	/* 保存注册的信号事件 */
	struct event_signal_map sigmap;
	/* 这是一个最小堆,用来保存注册的超时事件 */
	struct min_heap timeheap;
};

二,创建和初始化event_base

1、创建默认的event_base

创建一个 event_base 对象也既是创建了一个新的 libevent 实例,event_base_new()函数分配并且返回一个新的具有默认设置的 event_base。一般情况下整个默认的event_base 对象就能满足需求。

2、创建复杂的event_base

如果要创建复杂的event_base 对象就需要使用 event_config,下面是一些常用的接口

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);

先调用 event_config_new()分配一个event_config。 然后,对event_config调用其它函数,设置所需要的event_base特征。最后,调用 event_base_new_with_config()创建新的 event_base。完成创建工作后,使用event_config_free()释放event_config。下面是设置event_base特征的接口

接口:int event_config_avoid_method(struct event_config *cfg, const char *method);
功能:让libevent避免使用特定的I/O多路复用机制
示例:
/* 不使用select与poll机制 */
event_config_avoid_method(cfg, "select");
event_config_avoid_method(cfg, "poll");


接口:int event_config_require_features(struct event_config *cfg, enum event_method_feature feature);
功能:让libevent使用所指定特征的I/O多路复用机制
示例:
enum event_method_feature {
    /* 要求支持边沿触发的I/O多路复用机制 */
    EV_FEATURE_ET = 0x01,
    /* 要求添加、删除单个事件或者确定哪个事件激活的操作是 O(1)复杂度的I/O多路复用机制 */
    EV_FEATURE_O1 = 0x02,
    /* 要求支持任意文件描述符,而不仅仅是套接字的I/O多路复用机制 */
    EV_FEATURE_FDS = 0x04,
};


接口:int event_config_require_features(struct event_config *cfg, enum event_method_feature feature);
功能:libevent 在创建 event_base 时设置一个或者多个运行时标志
示例:
enum event_base_config_flag {
	/* 不要为 event_base 分配锁,设置这个选项可以 为 event_base 节省一点用于锁定和解锁的时间,但是让在多个线程中访问 event_base 成为不安全的 */
	EVENT_BASE_FLAG_NOLOCK = 0x01,
	EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
	EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
	EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
	EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
	EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};

三,接口函数

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);

1、事件注册

函数原型:int event_add(struct event *ev, const struct timeval *tv)
参数:
ev:指向要注册的事件
tv:超时时间
函数功能:函数将 ev 注册到 ev->ev_base 上,事件类型由 ev->ev_events 指明,如果注册成功,ev将被插入到已注册链表中;如果 tv 不是 NULL,则会同时注册定时事件,将 ev 添加到 timer堆上。下面是具体的代码实现,如下:

/* ****************************************************************************
 函 数 名  : event_add_nolock_
 功能描述  : 注册事件
 输入参数  : ev    : 要注册的事件
            tv    : 超时时间
			tv_is_absolute    : 用来判断传入的时间是绝对时间还是相对时间,1:绝对时间;0:相对时间(即:是一个时长,例如:2s)
 返 回 值  : 成功 0, 失败 -1
**************************************************************************** */
int event_add_nolock_(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
	struct event_base *base = ev->ev_base;
	int res = 0;
	int notify = 0;

	/* 如果event设置了超时时间,并且event所设超时结构体不在time小根堆上,则在time小根堆中预留空间 */
	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 */
	}

	/* 如果是I/O事件或信号事件,并且事件ev不在已注册或者激活链表中 */
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
		/* 如果是I/O事件,把事件添加到I/O链表 */
		if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
			res = evmap_io_add_(base, ev->ev_fd, ev);
		/* 如果是信号事件,把事件添加到信号链表 */
		else if (ev->ev_events & EV_SIGNAL)
			res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
		if (res != -1)
			event_queue_insert_inserted(base, ev);

		/* res == 1 说明监控的事件有变更 */
		if (res == 1) {
			/* 需要通知main thread,即:运行事件循环的线程,也就是调用event_base_loop的线程 */
			notify = 1;
			res = 0;
		}
	}

	/* 准备添加定时事件 */
	if (res != -1 && tv != NULL) {
		struct timeval now;
		int common_timeout;
#ifdef USE_REINSERT_TIMEOUT
		int was_common;
		int old_timeout_idx;
#endif

		/* 如果业务侧把这个事件设置成EV_PERSIST(即:永久事件),并且是相对超时时间(即:设置的是一个超时时长) */
		if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
			/* 记录业务侧设置的超时时长 */
			ev->ev_io_timeout = *tv;

#ifndef USE_REINSERT_TIMEOUT
		if (ev->ev_flags & EVLIST_TIMEOUT) {
			event_queue_remove_timeout(base, ev);
		}
#endif

		gettime(base, &now);

		common_timeout = is_common_timeout(tv, base);
#ifdef USE_REINSERT_TIMEOUT
		was_common = is_common_timeout(&ev->ev_timeout, base);
		old_timeout_idx = COMMON_TIMEOUT_IDX(&ev->ev_timeout);
#endif

		/* 如果是绝对时间 */
		if (tv_is_absolute) {
			ev->ev_timeout = *tv;
		} else if (common_timeout) {
			/* 超时时间在公共队列 */
			struct timeval tmp = *tv;
			tmp.tv_usec &= MICROSECONDS_MASK;
			evutil_timeradd(&now, &tmp, &ev->ev_timeout);
			/* 公共超时时间带上标志 */
			ev->ev_timeout.tv_usec |= (tv->tv_usec & ~MICROSECONDS_MASK);
		} else { 
			/* 设置的是一个超时时长,并且超时时长不是common-timeout */
			/* now表示的是当前时间,tv是超时时间,ev->ev_timeout保存的就是超时后的时间 */
			evutil_timeradd(&now, tv, &ev->ev_timeout);
		}

#ifdef USE_REINSERT_TIMEOUT
		event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
#else
		/* 将超时事件插入到超时队列(common-timeout 队列或小根堆) */
		event_queue_insert_timeout(base, ev);
#endif

		/* 如果是一个公共超时时间 */
		if (common_timeout) {
			struct common_timeout_list *ctl =
			    get_common_timeout_list(base, &ev->ev_timeout);
			/* 如果插入的事件是队列的第一个事件,注册一个超时事件 */
			if (ev == TAILQ_FIRST(&ctl->events)) {
				common_timeout_schedule(ctl, &now, ev);
			}
		} else {
			struct event* top = NULL;
			if (min_heap_elt_is_top_(ev))
				/* 如果本次插入的是最小的,就需要唤醒主线程,告诉其最小超时值已经变了 */
				notify = 1;
				/* 如果小根堆堆顶的节点(即:拥有最小超时时间的节点)对应的超时时间小于当前时间,说明当前堆顶的节点已经超时 */
			else if ((top = min_heap_top_(&base->timeheap)) != NULL &&
					 evutil_timercmp(&top->ev_timeout, &now, <))
				notify = 1;
		}
	}

	/* 如果当前线程不是主线程且注册事件成功,就唤醒主线程 */
	if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
		evthread_notify_base(base);

	event_debug_note_add_(ev);

	return (res);
}

2、事件删除

函数原型:int event_del(struct event *ev);
参数:
ev:要删除的事件
函数功能:该函数将删除事件 ev,对于 I/O 事件,从 I/O 的 demultiplexer 上将事件注销;对于 Signal事件,将从 Signal 事件链表中删除;对于定时事件,将从堆上删除。下面是具体的代码实现,如下:

int event_del_nolock_(struct event *ev, int blocking)
{
	struct event_base *base;
	int res = 0, notify = 0;

	/* An event without a base has not been added */
	if (ev->ev_base == NULL)
		return (-1);

	base = ev->ev_base;

	EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

	/* 如果事件在超时队列中 */
	if (ev->ev_flags & EVLIST_TIMEOUT) {
		event_queue_remove_timeout(base, ev);
	}

	/* 如果事件已经激活 */
	if (ev->ev_flags & EVLIST_ACTIVE)
		event_queue_remove_active(base, event_to_event_callback(ev));
	else if (ev->ev_flags & EVLIST_ACTIVE_LATER)
		event_queue_remove_active_later(base, event_to_event_callback(ev));

	/* 如果事件已经添加到队列 */
	if (ev->ev_flags & EVLIST_INSERTED) {
		event_queue_remove_inserted(base, ev);
		if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
			res = evmap_io_del_(base, ev->ev_fd, ev);
		else
			res = evmap_signal_del_(base, (int)ev->ev_fd, ev);
		if (res == 1) {
			/* evmap says we need to notify the main thread. */
			notify = 1;
			res = 0;
		}
		if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base))
			notify = 1;
	}

	/* if we are not in the right thread, we need to wake up the loop */
	if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
		evthread_notify_base(base);

	event_debug_note_del_(ev);

	return (res);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值