libevent如何管理event

一 event简介 

    libevent是以event为核心的reactor模型高性能的网络库,事件类型为io时间,定时时间和信号signal事件三种。libevent巧妙的将三种时间统一起来处理极大的简化了网络程序的复杂度。那么libevent是如何管理这三种event呢?

struct event {
	TAILQ_ENTRY(event) ev_active_next;
	TAILQ_ENTRY(event) ev_next;
	/* for managing timeouts */
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		int min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;

	struct event_base *ev_base;

	union {
		/* used for io events */
		struct {
			TAILQ_ENTRY(event) ev_io_next;
			struct timeval ev_timeout;
		} ev_io;

		/* used by signal events */
		struct {
			TAILQ_ENTRY(event) ev_signal_next;
			short ev_ncalls;
			/* Allows deletes in callback */
			short *ev_pncalls;
		} ev_signal;
	} _ev;

	short ev_events;
	short ev_res;		/* result passed to event callback */
	short ev_flags;
	ev_uint8_t ev_pri;	/* smaller numbers are higher priority */
	ev_uint8_t ev_closure;
	struct timeval ev_timeout;

	/* allows us to adopt for different types of events */
	void (*ev_callback)(evutil_socket_t, short, void *arg);
	void *ev_arg;
};
    libevent使用双向链表保存所有注册的io时间和signal事件,event_next就是该事件在双向链表中的位置指针。除此之外,libevent会把已激活的时间放入一个双向链表,保存所有的已激活事件,ev_active_next就表示了该事件在激活链表中的位置。 min_heap_idx 和 ev_timeout,如果是 timeout 事件,它们是 event 在小根堆中的索引和超时值, libevent 使用小根堆来管理定时事件, ev_base 该事件所属的反应堆实例,这是一个 event_base 结构体,下一节将会详细讲解, ev_fd,对于 I/O 事件,是绑定的文件描述符;对于 signal 事件,是绑定的信号,ev_callback, event 的回调函数,被 ev_base 调用,执行事件处理程序,这是一个函数指针。

    从 event 结构体中的 2个链表节点指针和一个堆索引出发,大体上也能窥出 libevent 对event 的管理方法了,可以参见下面的示意图。每次当有事件 event 转变为就绪状态时,libevent 就会把它移入到 active event list[priority]中,其中 priority 是 event 的优先级;接着 libevent 会根据自己的调度策略选择就绪事件,调用其 cb_callback()函数执行事件处理,并根据就绪的句柄和事件类型填充 cb_callback 函数的参数。

  

二 libevent如何将io,signal和timer时间统一管理

     首先我们要了解libevent时间主循环的执行流程:

                                                            

(1)io和timer时间的统一

      Libevent 将 Timer 和 Signal 事件都统一到了系统的 I/O 的 demultiplex 机制中了,首先将 Timer 事件融合到系统 I/O 多路复用机制中,还是相当清晰的,因为系统的 I/O机制像 select()和 epoll_wait()都允许程序制定一个最大等待时间(也称为最大超时时间)timeout,即使没有 I/O 事件发生,它们也保证能在 timeout 时间内返回。那么根据所有 Timer 事件的最小超时时间来设置系统 I/O 的 timeout 时间;当系统 I/O返回时,再激活所有就绪的 Timer 事件就可以了,这样就能将 Timer 事件完美的融合到系统的 I/O 机制中了。这是在 Reactor 和 Proactor 模式(主动器模式,比如 Windows 上的 IOCP)中处理 Timer事件的经典方法了, ACE 采用的也是这种方法。堆是一种经典的数据结构,向堆中插入、删除元素时间复杂度都是 O(lgN), N 为堆中元素的个数,而获取最小 key 值(小根堆)的复杂度为 O(1),因此变成了管理 Timer 事件的绝佳人选(当然是非唯一的), libevent 就是采用的堆结构。

(2) 信号处理

      基本方法就是采用“消息机制”。在 libevent 中这是通过 socket pair 完成的,下面就来详细分析一下。Socket pair 就是一个 socket 对,包含两个 socket,一个读 socket,一个写 socket。工作方式如下图所示:

  

     创建一个 socket pair 并不是复杂的操作,可以参见下面的流程图,清晰起见,其中忽略了一些错误处理和检查。

    

                                               

       Socket pair 创建好了,可是 libevent 的事件主循环还是不知道 Signal 是否发生了啊,我们还差最后一步,那就是: 为 socket pair 的读 socket 在 libevent 的 event_base 实例上注册一个 persist 的读事件。这样当向写 socket 写入数据时,读 socket 就会得到通知,触发读事件,从而 event_base就能相应的得到通知了,实际上就是在event_base中一个server,然后监听一个连接是否有数据可读。完整的处理框架如下所示:

                                           

 

        

猜你喜欢

转载自blog.csdn.net/d_guco/article/details/79119956