libevent : struct event & struct event_base
来源:互联网 发布:建筑作品集知乎 编辑:程序博客网 时间:2024/05/16 12:29
原文: http://www.cnblogs.com/hustcat/archive/2010/08/31/1814022.html
libevent将I/O事件、定时器、信号进行统一处理,也就是所谓的“统一事件源”(见《Linux高性能服务器编程》)。
结构体event代表一个事件。结构体event_base是处理事件的框架。
//event.hstruct event {TAILQ_ENTRY (event) ev_next; //已注册事件链表TAILQ_ENTRY (event) ev_active_next;//就绪事件链表TAILQ_ENTRY (event) ev_signal_next;//signal链表unsigned int min_heap_idx; /* for managing timeouts,事件在堆中的下标 */struct event_base *ev_base;int ev_fd; //对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号short ev_events; //event关注的事件类型short ev_ncalls; //事件就绪执行时,调用 ev_callback 的次数short *ev_pncalls; /* Allows deletes in callback */struct timeval ev_timeout; //timout事件的超时值int ev_pri; /* smaller numbers are higher priority,优先级 */void (*ev_callback)(int, short, void *arg); //回调函数void *ev_arg; //回调函数的参数int ev_res; /* result passed to event callback */int ev_flags; //event的状态};
各个字段的具体含义:
(1) ev_events:event关注的事件类型,它可以是以下3种类型:
I/O事件: EV_WRITE和EV_READ
定时事件:EV_TIMEOUT
信号: EV_SIGNAL
辅助选项:EV_PERSIST,表明是一个永久事件
libevent中的定义为:
#define EV_TIMEOUT 0x01#define EV_READ 0x02#define EV_WRITE 0x04#define EV_SIGNAL 0x08#define EV_PERSIST 0x10 /* Persistant event */
(2)ev_next,ev_active_next 和 ev_signal_next 都是双向链表节点指针;它们是 libevent 对不同事件类型和在不同的时期,对事件的管理时使用到的字段。
libevent 使用双向链表保存所有注册的 I/O和 Signal 事件,ev_next 就是该I/O事件在链表中的位置;此链表可以称为“已注册事件链表”;
同样 ev_signal_next 就是 signal 事件在 signal 事件链表中的位置;
ev_active_next:libevent 将所有的激活事件放入到链表 active list 中,然后遍历 active list 执
行调度,ev_active_next就指明了 event 在active list 中的位置;
(3)min_heap_idx 和 ev_timeout,如果是 timeout 事件,它们是 event 在小根堆中的索引和超时值,libevent 使用小根堆来管理定时事件。
(4)ev_base指向事件框架实例。
(5)ev_fd,对于 I/O事件,是绑定的文件描述符;对于 signal 事件,是事件对应的信号;
(6)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 已被初始化
//evenet_internal.hstruct event_base { const struct eventop *evsel; //底层具体I/O demultiplex操作函数集 void *evbase; 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; int nactivequeues;//就绪事件队列个数 /* signal handling info */ struct evsignal_info sig; //用于管理信号 struct event_list eventqueue; //注册事件队列 struct timeval event_tv; struct min_heap timeheap; //管理定时器的小根堆 struct timeval tv_cache; //记录时间缓存};
(1)evsel:libevent支持Linux、Windows等多种平台,也支持epoll、poll、select、kqueue等多种I/O多路复用模型。如果把event_init、event_add看成高层抽象的统一事件操作接口,则evsel为这些函数在底层具体的I/O demultiplex的对应的操作函数集。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;};
在初始化函数event_base_new中,libevent将evsel指向全局数组eventops的具体元素:
//I/O multiplex机制实例数组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};
主要函数分析
event_base_new的主要逻辑:
struct event_base * event_base_new(void){ //初始化小根堆 min_heap_ctor(&base->timeheap); //初始化注册事件队列 TAILQ_INIT(&base->eventqueue); for (i = 0; eventops[i] && !base->evbase; i++) { //I/O demultiplex机制实例 base->evsel = eventops[i]; //初始化I/O demultiplex实例(参见win32_init) base->evbase = base->evsel->init(base); } //分配1个就绪事件队列 event_base_priority_init(base, 1);}
注册事件 int event_add(struct event *ev, const struct timeval *tv)
该函数主要将事件ev加入到事件框架event_base的注册事件链表base->eventqueue。
删除事件 int event_del(struct event *ev)
该函数主要将事件ev从相应的链表上删除。
/** * 设置event对象 * ev:事件对象 * fd:事件对应的文件描述符或信号,对于定时器设为-1 * events:事件类型,比如 EV_READ,EV_PERSIST, EV_WRITE, EV_SIGNAL * callback:事件的回调函数 * arg:回调函数参数 */voidevent_set(struct event *ev, int fd, short events, void (*callback)(int, short, void *), void *arg)
在将事件注册事件处理框架之前,应该先调用event_set对事件进行相关设置。
libevent对event的管理
event结构有3个链表结点域和一个小根堆索引,libevent通过3个链表和一个小根堆对I/O事件、signal事件和timer事件进行管理。
对于I/O事件,通过event_add将其加入event_base的注册事件链表eventqueue ;
就绪时会加入event_base的就绪链表activequeues[];
对于timer事件,event_add将其加入到event_base的小根堆timeheap;
Signal事件的管理相对复杂些,event_add将其加入到注册事件链表,同时,event_add内部会调用I/O demultiplex的add函数(对于I/O事件也一样),比如epoll_add。而add函数又会调用evsignal_add将其加入到evsignal_info的evsigevents[signo]链表(关于signal,后面会详细介绍)。
事件处理框架主循环
Libevent将I/O事件、signal事件和timer事件用统一的模型进行处理,这是非常精妙的。libevent主循环函数不断检测注册事件,如果有事件发生,则将其放入就绪链表,并调用事件的回调函数,完成业务逻辑处理。
- event_dispatch
//事件处理主循环
int event_dispatch(void)
这是呈现给外部的接口,它的实现很简单,即调用event_loop,而event_loop调用event_base_loop,event_base_loop完成实际的主循环逻辑。
done = 0;while (!done) {/* 如果没有就绪事件,根据timer heap中事件的最小超时时间,计算I/O demultiplex的 最大等待时间. 相反,如果有就绪事件,则清除tv,即I/O demultiplex不应该等待.*/if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { timeout_next(base, &tv_p);} else { /* * 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);}//tv_p为I/O demultiplex的超时时间//处理signal事件和I/O事件res = evsel->dispatch(base, evbase, tv_p);//处理timeout事件,对于超时的事件,将其放到就绪事件链表timeout_process(base);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;}//end while
dispatch函数
调用底层I/O multiplex的dispatch函数,具体的实现可以参见epoll的实现epoll_dispatch。Timer事件
Timer事件的处理本身比较简单,不再赘述。signal事件
socket pair
Libevent通过socketpair,将signal事件与I/O事件完美的统一起来。Socketpair,简单的说就一对socket,一端用于写,一端用于读。工作方式如下:
为了与I/O事件统一起来,libevent内部使用了一个针对read socket的读事件。
Socketpair的创建
与信号事件的初始化工作都是在evsignal_init中完成的,而evsignal_init通过调用evutil_socketpair创建socketpair。对于Unix平台,有socketpair系统调用;对于Windows,则相对复杂一些,具体见evutil_socketpair函数的实现。
evsignal_info
在event_base内部有一个evsignal_info类型的字段sig,它是用于管理signal事件的核心数据结构:
//evsignal.hstruct evsignal_info {struct event ev_signal; //内部socket读事件int ev_signal_pair[2]; //对应socket pair的两个socket描述符int ev_signal_added; //内部socket读事件是否已经加入注册链表volatile sig_atomic_t evsignal_caught; //是否有信号发生//信号事件链表数组,evsigevents[signo]表示注册信号signo的事件struct event_list evsigevents[NSIG]; //具体记录每个信号触发的次数,evsigcaught[signo]是记录信号 signo被触发的次数sig_atomic_t evsigcaught[NSIG];//sh_old记录了原来的 signal 处理函数指针,当信号 signo 注册的 event 被清空时,需要重新设置其处理函数#ifdef HAVE_SIGACTIONstruct sigaction **sh_old;#elseev_sighandler_t **sh_old;#endifint sh_old_max;};
evsignal_init
主要完成evsignal_info的初始化,主要算法:
int evsignal_init(struct event_base *base){ evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair); base->sig.sh_old = NULL; base->sig.sh_old_max = 0; //事件发生次数设为0 base->sig.evsignal_caught = 0; memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG); /* initialize the queues for all events */ for (i = 0; i < NSIG; ++i) TAILQ_INIT(&base->sig.evsigevents[i]); evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); //写端 //设置内部读事件 event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal); //读端 base->sig.ev_signal.ev_base = base; //sig.ev_signal == EV_READ | EV_PERSIST | EVLIST_INTERNAL base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;}
该函数的关键在于这里会设置libevent用于管理信号事件的内部读事件evsignal_info的ev_signal,并将该事件对应的文件描述符设为socket pair的读端。该函数由I/O multiplex的init函数调用。注:这里只是设置,而并没有注册socket pair的读事件(见下一节)。
evsignal_add
当调用event_add注册信号事件时,内部会先调用I/O multiplex的add函数,add函数又会调用evsignal_add,将事件加到evsignal_info内部的信号事件链表。然后再event_queue_insert将其添加到event_base的注册事件链表。
int evsignal_add(struct event *ev){ int evsignal; struct event_base *base = ev->ev_base; struct evsignal_info *sig = &ev->ev_base->sig; //信号事件与I/O事件是不相容的 if (ev->ev_events & (EV_READ|EV_WRITE)) event_errx(1, "%s: EV_SIGNAL incompatible use", __func__); //事件的信号 evsignal = EVENT_SIGNAL(ev); assert(evsignal >= 0 && evsignal < NSIG); if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) { event_debug(("%s: %p: changing signal handler", __func__, ev)); //注册信号处理函数evsignal_handler //向socket pair的写端写入一个字节的数据 if (_evsignal_set_handler( base, evsignal, evsignal_handler) == -1) return (-1); /* catch signals if they happen quickly */ evsignal_base = base; //添加内部socket读事件sig->ev_signal if (!sig->ev_signal_added) { if (event_add(&sig->ev_signal, NULL)) return (-1); sig->ev_signal_added = 1; } } /* multiple events may listen to the same signal */ //添加信号事件链表 TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next); return (0);}
这里有两个地方需要注意,一是调用_evsignal_set_handler设置外部注册信号事件对应的信号的信号处理函数evsignal_handler:
static void evsignal_handler(int sig){ int save_errno = errno; //设置信号事件的发生次数 evsignal_base->sig.evsigcaught[sig]++; evsignal_base->sig.evsignal_caught = 1;#ifndef HAVE_SIGACTION signal(sig, evsignal_handler);#endif /* Wake up our notification mechanism */ //向socket pair的写端写数据 send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0); errno = save_errno;}
当用户注册信号事件对应的信号发生时,OS转到evsignal_handler函数,从而设置sig.evsignal_caught,并向socket pair的写端发送数据。
二是通过调用event_add完成内部socket pair的读事件sig->ev_signal的注册。最后,将(外部)事件添加到信号事件链表
与主循环结合
信号事件完成了注册,libevent就会在主循环中,等待事件发生,并处理事件。为了理解,来看看具体I/O demultiplex的dispatch函数:
static intepoll_dispatch(struct event_base *base, void *arg, struct timeval *tv){ struct epollop *epollop = arg; struct epoll_event *events = epollop->events; struct evepoll *evep; int i, res, timeout = -1; if (tv != NULL) timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { /* Linux kernels can wait forever if the timeout is too big; * see comment on MAX_EPOLL_TIMEOUT_MSEC. */ timeout = MAX_EPOLL_TIMEOUT_MSEC; } //等待I/O事件 res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); if (res == -1) { if (errno != EINTR) { event_warn("epoll_wait"); return (-1); } //epoll_wait被信号中断 evsignal_process(base); return (0); } else if (base->sig.evsignal_caught) {//发生了信号事件 //处理信号事件 evsignal_process(base); }//…}
epoll_dispatch函数调用epoll_wait函数等待I/O发生。然后,如果有信号事件发生,则调用evsignal_process处理信号事件,evsignal_process的逻辑比较简单,它只是将事件从注册事件链表转移到就绪事件链表。
还记得evsignal_handler函数吗?它是所有(外部)信号事件对应的信号的信号处理函数,将实际的信号发生时,OS会转而执行evsignal_handler函数,而它便向socket pair的写端写数据,而读端收到数据。而此时,libevent的内部socket pair读事件已经完成注册。libevent正阻塞在epoll_wait处,当socketp pair读端收到数据时,libevent便从epoll_wait处返回。总之,signal事件通过socket pair,与I/O事件实现完美的统一。
Libevent从epoll_wait返回后,它调用evsignal_process处理信号事件,然后调用event_active将I/O事件(包括内部的socket pair读事件)转移到就绪事件链表。
Socket pair的读事件回调函数:
static voidevsignal_cb(int fd, short what, void *arg){ static char signals[1]; #ifdef WIN32 SSIZE_T n; #else ssize_t n; #endif //接收数据 n = recv(fd, signals, sizeof(signals), 0); if (n == -1) event_err(1, "%s: read", __func__);}
libevent的应用
Libevent是一个非常优秀的开源网络库,它被许多其它开源程序使用。Memcache使用libevent作为底层的网络处理组件,并采用主线程(main thread,单一)+工作线程(work thread,多个)的多线程模型(这将在Memcached的分析中详细介绍)。
总结
关于统一事件源,可以看《Linux高性能服务器编程》的第十章,第四节。
- libevent : struct event & struct event_base
- libevent事件处理程序struct event解析
- libevent---核心event和event_base
- libevent源码分析(3)--2.1.8--结构体struct event_base和struct eventop
- struct event
- Libevent(2)— event、event_base
- Libevent(2)— event、event_base
- [libevent]event,event_base结构体描述
- libevent使用(二) ----- event_base 和 event
- Libevent— event、event_base(常用api)
- [libevent]event/event_base/epollop/evsignal_info分析
- struct event无定义?
- libevent源码分析(2)--2.1.8--结构体 struct event和struct event_callback
- libevent源码详解(三)数据结构之event、event_base
- Struct
- struct
- struct
- struct
- MyEclipse 启动 Tomcat 速度慢
- RecyclerView使用详解
- BigDecimal如何比较大小
- storm实战入门
- SimpleCursorAdapter-获取电话簿中联系人并显示在类表中
- libevent : struct event & struct event_base
- Android中跨进程通信方式之使用Messenger
- 2016.5.10
- 光源(Light)
- 第十一周阅读程序(5.5)
- JVM系统属性
- 唯爱小粽子:Java中break和continue的区别
- java 内存映射文件
- Java Web前端到后台常用框架介绍