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};
接下来我们来按照流程图剖析一下整体流程。
整个流程用一段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)
- libevent的event处理框架剖析
- Libevent信号event的处理
- Libevent源码分析-----信号event的处理
- Libevent源码分析-----超时event的处理
- Libevent源码分析-----信号event的处理
- Libevent源码分析-----超时event的处理
- Libevent源码分析-----信号event的处理
- Libevent源码分析-----超时event的处理
- libevent源码深度剖析五 ——libevent的核心:事件event
- Libevent源码剖析——事件event
- libevent的min_heap剖析
- libevent的bufferevent剖析
- Libevent源码分析-event处理流程
- libevent事件处理程序struct event解析
- libevent事件处理框架分析
- libevent事件处理框架分析
- libevent事件处理框架分析
- libevent的epoll模式剖析
- 未能加载文件或程序集“Interop.zkemkeeper”或它的某一个依赖项。试图加载格式不正确的程序。
- html+css+js(JQuery)使用笔记(随时更新)
- Qt程式异常崩溃处理技巧(Win)
- poj 1741 Tree 点分治
- 学习Hadoop第十九课(Zookeeper集群搭建)
- libevent的event处理框架剖析
- ajax在兼容模式下失效的快速解决方法
- Android中的Service浅析
- Eclipse Neon 使用Git与Github 方法与问题
- [Android]主题和样式
- 使用WritePrinter打印图片
- redis持久化
- FlashBuilder 4.6 破解序列号和方法
- vs2010彻底解决 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏