前几次剖析了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
};
接下来我们来按照流程图剖析一下整体流程。
整个流程用一段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)