#define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* previous element */ \ }放置在event结构体中,将event串成队列。
#define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* 队列头*/ \ struct type **tqh_last; /* 队列尾 */ \ }
event_base 代表一个libevent实体。其结构如下:
struct event_base { const struct eventop *evsel; /* evbase对应的操作函数 */ void *evbase; /* 采用何种IO多路复用函数 */ int event_count; /* 事件总量 */ int event_count_active; /* 激活事件数量 */ int event_gotterm; /* 优雅的结束,处理完激活事件队列中事件后结束 */ int event_break; /* 立即结束 */ struct event_list **activequeues; /* 多个激活事件队列 */ int nactivequeues; /* 队列个数,0最高优先级 */ struct evsignal_info sig; /* 信号结构体 */ struct event_list eventqueue; /* 信号/IO事件队列 */ struct min_heap timeheap; /* 时间堆 */ struct timeval tv_cache; /* 缓存时间 */ struct timeval event_tv; /* 事件时间 */ };1. evsel 与 evbase
evsel 代表操作集合。
const struct eventop epollops = { "epoll", epoll_init, epoll_add, epoll_del, epoll_dispatch, epoll_dealloc, 1 /* need reinit */ };evbase 代表采用哪种IO多路复用函数, epoll, select, poll等。
struct epollop { struct evepoll *fds; int nfds; struct epoll_event *events; int nevents; int epfd; };上述两者配对使用,evbase 相当于C++的this指针,而epollops内的函数相当于类的成员函数。
2. activequeues
activequeues队列是一个指针数组,数组中每个元素可以指向一个事件队列;数组索引表示优先级,0代表最高优先级。
当处理事件时候,从头扫描数组,若发现非空事件队列,进行处理,只有当前优先级队列处理完成后,才进行下一优先级的处理。
3. sig、eventqueue、timeheap
上述三个变量分别用于处理信号事件,信号与IO事件队列,定时器事件小根堆。
4. tv_cache 、 event_tv
由于获取时间需要调用系统调用,这是一个耗时操作,因此libevent将时间缓存,减少系统调用次数,从而达到加速的目的。
event_tv 用于修正时间,后续时间管理章节说明。
struct event { TAILQ_ENTRY (event) ev_next; /* IO、信号事件结点 */ TAILQ_ENTRY (event) ev_active_next; /* 激活事件结点 */ TAILQ_ENTRY (event) ev_signal_next; /* 信号事件结点 */ unsigned int min_heap_idx; /* 时间堆中索引位置 */ struct event_base *ev_base; /* 指向event_base 结构体 */ int ev_fd; /* 用户套接字 或 信号*/ short ev_events; /* 事件类型,可读,可写,持久,超时 */ short ev_ncalls; /* 事件回调函数需要被调用次数 */ short *ev_pncalls; /* Allows deletes in callback */ struct timeval ev_timeout; /* 超时时刻 */ int ev_pri; /* 事件优先级,0最高优先级 */ void (*ev_callback)(int, short, void *arg);/* 事件回调函数 */ void *ev_arg; /* 回调函数参数 */ int ev_res; /* result passed to event callback */ int ev_flags; /* 事件目前状态, TIMEOUT, INIT, SIGNAL */ };
上述四个变量比较易于理解,分别是事件在不同队列中的一个节点。有一个奇怪点,信号事件既在ev_next中,
又在ev_signal_next中。当用户添加信号事件的时候,会在事件队列中添加,同时,也会在该信号对应的队列中添加。
允许同一信号,注册不同的事件,这样signal[sig no] 将会挂一个链表,如下图。详细内容在信号一节中说明。
(图片来自互联网)
2. ev_events
#define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 /* Persistant event */在添加事件的时候指定事件类型,如果非持久事件,则事件在处理后被删除。
3. ev_flags
#define EVLIST_TIMEOUT 0x01 #define EVLIST_INSERTED 0x02 #define EVLIST_SIGNAL 0x04 #define EVLIST_ACTIVE 0x08 #define EVLIST_INTERNAL 0x10 #define EVLIST_INIT 0x80当前事件在内部的状态,是否已经超时,是否已经插入事件队列等等。
运行一个事件的回调函数被调用ev_ncalls次, 主要是用来记录信号,即同一事件对应的信号在一定时间内发生了n次,
则调用该事件回调函数n次。
5. ev_timeout
如果该事件为一个超时事件,那么ev_timeout记录该事件超时的时间点。比如当前时间是1:40, 那么用户指定10分钟后超时,
则ev_timeout为1:50.
6. ev_res
该参数传递给用户注册的事件回调函数,告诉用户,该事件是由于响应了什么事件而被激活的。例如:
if (evread != NULL) event_active(evread, EV_READ, 1);在epoll中,若一个事件可读,通过event_active将事件加入激活队列中去,此时, EV_READ表示该事件激活是由于可读。
可见ev_res的取值内容同ev_events一致。
void event_active(struct event *ev, int res, short ncalls) { ev->ev_res = res; event_queue_insert(ev->ev_base, ev, EVLIST_ACTIVE); }当处理该事件的时候,ev_res传递给用户回调函数。
while (ncalls) { ncalls--; ev->ev_ncalls = ncalls; (*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg); if (base->event_break) return; }插一句:上述可以看到如果event_break为真,立即返回,即使激活事件队列中还有激活事件,也不在进行处理了。