libevent是一个使用C语言编写的,轻量级的开源高性能网络库,使用者很多,研究者也很多。由于代码简洁,设计思想简明巧妙,因此很适合用来学习,提升自己C语言的能力。
libevent有这样显著地几个亮点:
1.事件驱动,高性能
2.轻量级,专注于网络,不如ACE那么庞大臃肿
3.代码精炼易读
4.跨平台,支持Windows,Linux,*BSD和Mac Os;
5.支持多种IO多路复用技术,epoll,poll,dev/poll、select和kqueue等
6.支持IO,定时器和信号等事件
7.注册事件优先级
基于以上优点,libevent已经被广泛的应用,作为底层的网络库;比如memcached、Vomit、Nylon、Netchat等。
下面我们就来从程序的基本使用场景和代码的整体处理流程入手来对libevent库进行学习。
当应用程序向libevent注册一个事件后,libevent内部是怎样处理的呢,以下是基本流程:
1)首先应用程序准备并初始化event,设置好事件类型和回调函数
2)向libevent添加该事件event。
3)程序调用event_base_dispatch()系列函数进入无线循环,等待事件.
这只是大概的流程,以下是对于流程中的一些基本概念的讲解:
一、事件event
libevent是基于事件驱动的,从名字上可以看出event是整个库的核心。首先给出event结构体的声明,它位于event.h文件中:
1 struct event { 2 TAILQ_ENTRY (event) ev_next; 3 TAILQ_ENTRY (event) ev_active_next; 4 TAILQ_ENTRY (event) ev_signal_next; 5 unsigned int min_heap_idx; /* for managing timeouts */ 6 struct event_base *ev_base; 7 int ev_fd; 8 short ev_events; 9 short ev_ncalls; 10 short *ev_pncalls; /* Allows deletes in callback */ 11 struct timeval ev_timeout; 12 int ev_pri; /* smaller numbers are higher priority */ 13 void (*ev_callback)(int, short, void *arg); 14 void *ev_arg; 15 int ev_res; /* result passed to event callback */ 16 int ev_flags; 17 };
1) ev_events:event关注的事件类型,它可以是以下3种类型:
IO事件:EV_WRITE和EV_READ
定时事件:EV_TIMEOUT
信号:EV_SIGNAL
辅助选项:EV_PERSIST,表明是一个永久事件
2) ev_next,ev_active_next,ev_signal_next都是双向链表节点指针;他们是libevent对不同事件类型和在不同的时期,对事件的管理时使用到的字段。
libevent使用双向链表保存所有注册的IO和Signal事件,ev_next就是该IO事件在链表中的位置,称此链表为“已注册事件链表”;
同样ev_signal_next就是signal事件的signal事件链表中的位置。
ev_active_next:libevent将所有激活事件放入到链表active list中,然后遍历active list执行调度。
每当事件event转变为就绪状态时,libevent就会把它移入到active event list[priority]中,其中priority是event的优先级;接着libevent会根据自己的调度策略选择就绪事件,调用其callback()函数执行事件处理;并根据就绪的句柄和时间类型填充callback函数的参数。
3)min_heap_idx和ev_timeout:如果事件是timeout事件,他们是event在小根堆中的索引和超时值,libevent使用小根堆来管理定时事件
4)ev_base:该事件所属反应堆实例,这是一个event_base结构体。
5)ev_fd:对于IO事件,这是绑定的文件描述符,对于signal事件,是绑定的信号
6)ev_callback:event的回调函数,被ev_base调用,执行事件处理程序,这是一个函数指针,原型为:
void (*ev_callback)(int fd,short events,void* arg)
其中参数fd对应于ev_fd;events对应于events;arg对应于ev_arg;
7)ev_arg:void*,表明可以是任意类型的数据,在设置event时指定;
8)eb_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已被初始化
9)ev_ncalls:事件就绪执行时,调用ev_callback的次数,通常为1
10)ev_res:记录了当前激活事件的类型
要想向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)
ev:执行要初始化的event对象
fd:对于信号来说是绑定的文件描述符,对于信号来说是绑定的signal信号
events:在该fd上关注的事件类型,它可以是EV_READ,EV_WRITE,EV_SIGNAL;
callback:这是一个函数指针,当fd上的事件event发生时,调用该函数执行处理
arg:传递给callback函数指针的参数
int event_base_set(struct event_base* base,struct event *ev)
设置event ev将要注册到的evnet_base;
int event_priority_set(struct event* ev,int pri)
设置event ev的优先级,注意,当ev正处于就绪状态时,不能设置,返回-1.
二、事件处理框架event_base
以下是base_event结构体的声明,它位于event-internal.h文件中:
1 struct event_base { 2 const struct eventop *evsel; 3 void *evbase; 4 int event_count; /* counts number of total events */ 5 int event_count_active; /* counts number of active events */ 6 int event_gotterm; /* Set to terminate loop */ 7 int event_break; /* Set to terminate loop immediately */ 8 /* active event management */ 9 struct event_list **activequeues; 10 int nactivequeues; 11 /* signal handling info */ 12 struct evsignal_info sig; 13 struct event_list eventqueue; 14 struct timeval event_tv; 15 struct min_heap timeheap; 16 struct timeval tv_cache; 17 };
以下是结构体各字段的含义:
1)evsel和evbase这两个字段的设置可能会让人有些迷惑,这里我们可以把evsel和evbase看做是类和静态函数的关系,比如添加事件时的调用行为:evsel->add(evbase,ev),实际执行操作的是evbase,这相当于class::add(instance,ev),instance就是class的一个对象实例。
2)activequeues是一个二级指针,前面讲过libevent支持事件优先级,因此你可以你把它看做是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event。
3)eventqueue:链表,保存了所有的注册事件event的指针。
4)sig是用来管理信号的结构体
5)timeheap是管理定时事件的小根堆
6)event_tv和tv_cache是libevent用于事件管理的变量
我们已经对event_base有了一个初步的了解,那么event_base如何创建和初始化的呢?
创建一个event_base对象也即是创建了一个新的libevent实例,程序需要通过调用event_init()函数来创建,该函数首先为event_base实例申请空间,然后初始化timer mini-heap,选择并初始化合适的系统多路复用机制,初始化各事件的链表;函数还检测了系统的时间设置,为后面的事件管理打下了基础
三、事件主循环
libevent将IO事件、定时器和信号事件处理很好的结合到了一起,那么它是如何做到的呢?
libevent的事件主循环主要是通过event_base_loop()函数完成的,主要操作流程如下图所示,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; // 清空时间缓存 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标记 if (base->event_gotterm) { base->event_gotterm = 0; break; } if (base->event_break) { base->event_break = 0; break; } // 校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间 // 在timeout_correct函数里,比较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 { // 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待 // 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理 evutil_timerclear(&tv); } // 如果当前没有注册事件,就退出 if (!event_haveevents(base)) { event_debug(("%s: no events registered.", __func__)); return (1); } // 更新last wait time,并清空time cache gettime(base, &base->event_tv); base->tv_cache.tv_sec = 0; // 调用系统I/O demultiplexer等待就绪I/O events,可能是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; } // 循环结束,清空时间缓存 base->tv_cache.tv_sec = 0; event_debug(("%s: asked to terminate loop.", __func__)); return (0); }