libevent
是基于事件驱动的, 事件驱动主要是由event_base
负责, 它是负责事件的各种处理(注册, 删除等); 而event
则是libevent的核心, 管理事件驱动.
event结构
其实说的一些东西只有看过了libevent大部分源码后才容易理解, 所以最开始的还都是从源码入手, 理解清楚了整个框架才能明白其中的意义.
struct event {
// 三种队列
// 注册的事件队列
TAILQ_ENTRY (event) ev_next;
// 就绪队列
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;
// IO : 事件的文件描述符
// 信号 : 以绑定的信号
int ev_fd;
// 监听的类型
short ev_events;
// 调用回调函数 ev_callback 的次数
short ev_ncalls;
// 删除回调, 通常指向 ev_ncalls 或者为 NULL
short *ev_pncalls; /* Allows deletes in callback */
// 设置定时时间
struct timeval ev_timeout;
int ev_pri; // 队列优先级, 与event_base *ev-base 中的优先级队列联用/* smaller numbers are higher priority */
// 设置的回调函数,
// int : ev_fd
// short : ev_event
// arg : ev_arg. 通过 event_set 设置第三个参数
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
// 回调时保存返回的事件状态.(像select中的fd_set, poll中的revent, epoll中的epoll_events结构)
int ev_res; /* result passed to event callback */
// 标志位: 标志当前event事件的状态, 以及所在哪个链表中
int ev_flags;
};
大部分的解释都在注释中了, 但是可能还有部分不太明了, 这里我具体对可能不太明了的进行解释.
-
TAILQ_ENTRY : 一个宏定义, 定义的是链表. 使用宏定义来实现链表很像c++中的模板. 如果对宏定义定义链表感兴趣可以在
compat/sys/queue.h
看一下.#define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev;/* 前一个节点的next节点的地址. 这样主要是方便删除操作*/ \ }
-
ev_callback :
event
的回调函数, 被event_base
调用. 执行事件处理程序.- ev_fd : 如果是 IO 类型,则ev_fd是文件描述符; 如果是 信号, 则ev_fd是绑定的具体信号.
- ev_event : 监听事件的类型.
- ev_arg : 是ev_callback的第三个参数.
- ev_res : ev_callback的返回事件的状态.
其中
ev_events
是libevent内部定义的类型. 针对IO, 信号, 定时都有一个细致的划分, 还有一个永久事件我们以后碰到了再来分析.// 定时事件 #define EV_TIMEOUT 0x01 // 读事件 #define EV_READ 0x02 // 写事件 #define EV_WRITE 0x04 // 信号事件 #define EV_SIGNAL 0x08 // 表事件为永久 #define EV_PERSIST 0x10 /* Persistant event */
-
ev_flags : 标志位. 表示当前
event
的状态是什么(还没有被初始化, 还是已经就绪等等状态)./**************** int 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 /* EVLIST_X_ Private space: 0x1000-0xf000 */ #define EVLIST_ALL (0xf000 | 0x9f)
说了这么多参数的意义, 如果是刚开始接触肯定是懵逼的, 这很正常. 现在我们来理一下整个event
结构, 一定要认真看一下, 不然很可能还是很懵逼.
-
三个队列.
event中定义的三个链表队列就是用来区分注册事件的状态. 比如事件还没有被注册则三个队列中都没有信息; 如果事件刚被注册了那么事件肯定在注册链表中并且
ev_flag
标志位设置为EVLIST_INSERTED
(标志好处就是我们不用在链表中遍历判断事件在哪一个链表中, 只需要判断标志位就可以); 如果注册的事件是IO事件, 那么还要ev_event
标志;如果注册的事件是信号, 那么还要将事件加入信号链表中并设置ev_flags
标志同时ev_event
标志也要设置为EV_SIGNAL
; 如果事件已经被触发了, 则要加事件加入就绪链表并设置标志还要从其他两个链表中删除.记住 : 在设置事件的时候需要设置
ev_event
标志位来标志事件的类型, 加入链表时需要设置ev_flags
标志来标志所处的哪个链表和状态.扫描二维码关注公众号,回复: 5294271 查看本文章 -
定时.
当事件设置了时间, 那么肯定要初始化
ev_timeout
标志, 同时将该事件加入到min_heap_idx
小根堆中, 时间越短越靠前也就越快被触发. -
回调
当事件从就绪链表中被取出时, 那就表示事件被触发, 通过查看
ev_ncalls
来执行对少次回调函数.
如果看完了上面的三个过程, 能够让你减少懵逼的话我也很高兴自己理的思路能够帮助你. 剩下的过程我们以后遇到再来分析, 毕竟event
可是核心, 怎么会这么简单.
总结
分析了event
结构可以看出来不管事件是IO, 信号还是定时事件都可以通过event
进行统一的封装, 这就是Reactor接口实现, 很像下面的事件收集器(虽然不太准确). 下面我们接着分析event_base
结构.