libevent的event处理框架剖析

来源:互联网 发布:java io流测试题 编辑:程序博客网 时间:2024/05/16 15:05

前几次剖析了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执行的次数,通常为1short *ev_pncalls;/* Allows deletes in callback */    //通常指向event_ncalls,或者为NULLstruct 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_argvoid *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机制的名字,来自eventopsvoid *evbase;     //接收init返回的I/O多路复用的op,如epollopint 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的就绪事件eventint 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,#endifNULL};


经过上面对几个关键结构体的分析,我们再来看一下这几个结构体的关系图(图非原创,出处忘了):

我们注册一个事件event,这个事件内部有一个指针,指向了一个反应堆event_base。event_base中有已注册事件队列,活跃事件队列,还有定时事件的最小堆。当我们调用event会根据该事件类型选择注册到哪个队列或者min_heap中,并且event_base还有反应堆的一系列属性,如时间缓存,总事件计数器,以及I/O multiplexer的方法。

流程图大概是下面这样:

接下来我们来按照流程图剖析一下整体流程。


整个流程用一段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。

voidevent_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;   //指向ncallsmin_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可以被关联进不同的反应堆中)。

intevent_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)     //只有无辜的事件可以分配给一个不同的basereturn (-1);ev->ev_base = base;ev->ev_pri = base->nactivequeues/2;  //优先级一半return (0);}
关联起来之后,接下来应该把event真正的放入event_base相应的事件队列中,调用event_add()函数,但是event_add()函数不仅在这种情况可以用,有可能某个事件已经被add过了,但是又想给它设置定时,或者换个定时时间,将其加入或者重新加入定时min_heap中,也要调用这个函数。

intevent_add(struct event *ev, const struct timeval *tv){struct event_base *base = ev->ev_base;  //要注册到的event_baseconst 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()函数负责:

intevent_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 <stdio.h>#include <event.h>#include <assert.h>#include <unistd.h>#include <string.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#define SERVER_IP   "127.0.0.1"#define SERVER_PORT 6666#define BACKLOG     5#define MAX_SIZE    1024struct 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)

1 0
原创粉丝点击