一、事件主循环
1、事件处理主流程
libevent的事件循环主要是通过event_base_loop函数来完成,其主要的操作如下:
1、根据timer-heap中的事件最小超时时间,计算系统I/O demultiplexer的最大等待时间。例如:当底层使用的是Linux提供的epoll机制时,这个最小超时时间就是传给epoll_wait系统调用的超时时间
2、调用系统I/O demultiplexer等待就绪I/O事件。例如:当底层使用的是Linux提供的epoll机制时,调用的是epoll_wait
3、检查signal的激活标志,如果被设置,则检查激活signal event并把event对应的事件回调插入到激活队列
4、将就绪I/O event对应的事件回调插入到激活队列
5、检查timer-heap中的timer-event,将就绪的timer-event从heap上删除,并把事件对应的回调插入到激活队列
6、根据优先级处理激活队列中的事件回调,执行回调函数处理激活事件
7、如果激活事件是超时事件,重新计算超时时间,并注册超时事件
2、开启事件循环的接口
下面是开启事件循环的接口,通过event_base_loop函数的第二个参数,可以修改这个函数的行为,如下:
int event_base_dispatch(struct event_base *event_base);
int event_base_loop(struct event_base *base, int flags);
/* 不设置标志就是直接调用event_base_dispatch,会一直运行事件循环,直到调用event_base_loopexit() 或 event_base_loopbreak() 或 没有要监控的事件 退出事件循环 */
/* 阻塞等待,当事件被激活并执行完回调后,退出事件循环(即:等待事件被触发一次后退出) */
#define EVLOOP_ONCE 0x01
/* 不阻塞,直接判断是否有激活事件,有激活事件直接指定对应的回调,执行完退出事件循环 */
#define EVLOOP_NONBLOCK 0x02
/* 一直运行事件循环,直到调用event_base_loopexit() 或 event_base_loopbreak() 退出事件循环*/
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
3、激活事件插入到激活队列
激活队列是一个优先级队列,当事件被激活时会根据事件的优先级把事件回调插入到激活队列。事件回调对应的类型是event_callback,它有一个evcb_pri成员记录事件回调的优先级,把激活事件回调插入到激活队列会经过下面的步骤:
1)evcallback_list *pList = &base->activequeues[evcb->evcb_pri]; // 根据事件优先级,获取对应的队列
2)事件回调(event_callback数据结构)插入到对应的优先级队列
下面是激活队列(二维列表)对应的数据结构,方框里面的数字代表优先级,如下:
4、处理激活队列中的事件
处理激活队列中的事件,大致按照下面的流程
1)遍历base->activequeues,依次获取对应的优先级队列(优先级值越小优先级越高,遍历时是从下标为0 的元素开始遍历的)
2)从优先级队列依次取下激活事件回调并执行对应的回调函数
4.1、事件回调对应的数据结构
struct event_callback数据结构记录与事件回调相关的信息,如下:
struct event_callback {
TAILQ_ENTRY(event_callback) evcb_active_next;
/* 记录事件的状态,即:激活的事件类型,例如:如果句柄可读导致事件被激活,这个字段的值就是EV_READ */
short evcb_flags;
/* 事件优先级 */
ev_uint8_t evcb_pri; /* smaller numbers are higher priority */
/* 记录事件回调类型,这个字段的赋值点是在event_assign函数 */
ev_uint8_t evcb_closure;
/* 根据不同类型的事件执行不同的回调函数 */
union {
void (*evcb_callback)(evutil_socket_t, short, void *);
void (*evcb_selfcb)(struct event_callback *, void *);
void (*evcb_evfinalize)(struct event *, void *);
void (*evcb_cbfinalize)(struct event_callback *, void *);
} evcb_cb_union;
/* 自定义参数 */
void *evcb_arg;
};
4.2、执行事件回调
调用event_new接口创建对应的事件,传入的回调类型是void (*cb)(evutil_socket_t, short, void *),在执行回调时需要传入这几个参数(句柄、事件触发类型、自定义参数),下面看下这几个参数分别是如何获取到的
1)句柄:event_callback是event的成员,当前可以拿到event_callback对应的地址,通过指针偏移就可以拿到对应的event,通过event就可以获取到对应的句柄
2)事件触发类型:event的ev_res成员记录了事件触发类型
3)自定义参数:event_callback的evcb_arg成员记录了自定义参数地址
5、停止事件循环
libevent内定义了两个函数,可以用于停止事件循环:
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
- event_base_loopexit()函数告诉event_base在指定的时间tv后停止循环。如果tv参数为空的话,event_base立即停止循环,如果event_base当前正在运行active events的回调,则直到运行完之后才会停止。
- event_base_loopbreak()函数告诉event_base立即退出循环,区别于event_base_loopexit(base, NULL),event_base_loopbreak()会在处理完当前active events之后立马退出。
注意:event_base_loopexit(base,NULL)和event_base_loopbreak(base)在没有事件循环运行时的行为不同:loopexit安排事件循环的下一个实例在下一轮回调运行后立即停止(就像它已经被调用一样与 EVLOOP_ONCE) 而loopbreak只停止当前正在运行的循环,如果事件循环没有运行,则无效。