I/O框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组更为便捷的接口
各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Proactor模式实现,要么同时用这两种模式。
拿基于Reactor模式的I/O框架库来说,包含以下几个组件:
1、句柄
I/O框架库要处理的对象,即I/O事件、信号和定时事件,统一称为事件源。一个事件源通常和一个句柄绑定在一起。句柄的作用是当内核检测到有事件就绪时,它就通过句柄来通知应用程序这件事,linux下通常就是 文件描述符。
2、事件多路分发器
I/O框架库一般将系统支持的各种I/O复用系统调用封转为统一的接口,称为事件多路分发器,内部调用 select 、poll 、epoll_wait等函数
当然,还要能提供添加事件和删除事件的功能
3、事件处理器和具体事件处理器
通常对应业务逻辑,包含一个或多个handler_event的回调函数,我们通常将 事件处理器和句柄绑定
4、Reactor
为框架库的核心,提供的几个主要方法是
handler_events, register_hander, remove_handler
现在让我们看看,Libevent中的事件处理器是event结构类型。event结构体封装了句柄,事件类型,回调函数,以及其他必要的标志和数据。
各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Proactor模式实现,要么同时用这两种模式。
拿基于Reactor模式的I/O框架库来说,包含以下几个组件:
1、句柄
I/O框架库要处理的对象,即I/O事件、信号和定时事件,统一称为事件源。一个事件源通常和一个句柄绑定在一起。句柄的作用是当内核检测到有事件就绪时,它就通过句柄来通知应用程序这件事,linux下通常就是 文件描述符。
2、事件多路分发器
I/O框架库一般将系统支持的各种I/O复用系统调用封转为统一的接口,称为事件多路分发器,内部调用 select 、poll 、epoll_wait等函数
当然,还要能提供添加事件和删除事件的功能
3、事件处理器和具体事件处理器
通常对应业务逻辑,包含一个或多个handler_event的回调函数,我们通常将 事件处理器和句柄绑定
4、Reactor
为框架库的核心,提供的几个主要方法是
handler_events, register_hander, remove_handler
现在让我们看看,Libevent中的事件处理器是event结构类型。event结构体封装了句柄,事件类型,回调函数,以及其他必要的标志和数据。
struct event
{
//所有被激活的事件处理器通过该成员串联成一个尾队列,我们称为活动事件队列。活动事件队列不止一个,不同优先级的事件处理器被激活后将被插入不同的活动事件队列中。
//在事件循环中,Reactor将按优先级从高到低遍历所有活动事件队列,并依次处理之
TAILQ_ENTRY(event) ev_active_next;
//指所有已经注册过的事件处理器(I/O事件处理器、信号事件处理器)通过该成员串联成一个尾队列,我们称为注册事件队列
//TAILQ_ENTRY是尾队列中的节点类型,定义在 compar/sys/queue.h 中
TAILQ_ENTRY(event) ev_next;
//代表事件类型,可以是
//#define EV_TIMEOUT 0X01 /*定时事件*/
//#define EV_READ 0X02 /*可读*/
//#define EV_WRITE 0X04 /*可写*/
//#define EV_SIGNAL 0X08 /*信号事件*/
//#define EV_PERSIST 0X10 /*永久事件*/
//#define EV_ET 0X20 /*需要I/O复用的支持,epoll*/
short ev_events;
//是联合体,仅用于定时器的处理
//在老版本中,定时器都是由时间堆来管理,但开发者认为有时候使用简单的链表来管理定时器将具有更高的效率,因此新版本引入了“通用定时器”
//通用定时器不是存储在时间堆中,而是在尾队列中,即通用定时器队列
//因此,无论是使用通用定时器,还是时间堆,联合体里的变量都代表当前定时器在所有定时器中的位置
//具体判断一个定时器是使用哪种方式存储可查看函数 is_common_timeout()
union {
TAILQ_ENTRY(event) ev_next_common_timeout;
int min_heap_idx;
}ev_timeout_pos;
//所有具有相同文件描述符的I/O处理事件处理器通过ev_io_next形成尾队列; 所有具有相同信号值的I/O处理事件处理器通过ev_signal_next形成尾队列;
//ev_ncalls指定信号发生时,Reactor需要执行多少次回调函数。 ev_pncalls要么为空,要么指向 ev_ncalls
//在程序中,我们可能针对一个socket文件描述符可读/可写事件创建多个事件处理器(回调函数不同)。当该文件描述符的可读/可写事件发生时,
//所有这些事件处理器都要被触发,所以,这里将他们组织在一起,这样,在事件发生时,可以很快的将所有相关事件添加到活动事件队列中去
union {
struct {
TAILQ_ENTRY(event) ev_io_next;
struct timeval ev_timeout;
}ev_io;
struct {
TAILQ_ENTRY(event) ev_signal_next;
short ev_ncalls;
short *ev_pncalls;
}ev_signal;
}_ev;
//可以是文件描述符 或是 信号值
evutil_socket_t ev_fd;
//对应的Reactor实例
struct event_base *ev_base;
//当前激活事件的类型
short ev_res;
//事件标志,可以是
//#define EVLIST_TIMEOUT 0X01 //事件处理器从属于通用定时器队列或是时间堆
//#define EVLIST_INSERTED 0X02 //从属于注册事件队列
//#define EVLIST_SIGNAL 0X04
//#define EVLIST_ACTIVE 0X08
//#define EVLIST_INTERNAL 0X10
//#define EVLIST_INIT 0X80 //事件处理器已被初始化
//#define EVLIST_ALL (0xf000 | 0x9f) //定义所有标志
short ev_flags;
//指定优先级,越小优先级越高
ev_uint8_t ev_pri;
//指定event_base执行事件处理器的回调函数时的行为
ev_uint8_t ev_closure;
//仅对定时器有效,指定超时时间
struct timeval ev_timeout;
//事件处理器的回调函数,由event_base调用
void (*ev_callback)(evutil_socket_t, short, void *arg);
//回调函数参数
void *ev_arg;
};
#define TAILQ_ENTRY(type) \
struct { \
struct type* tqe_next; \ /*下一个元素*/
struct type** tqe_prev; \ /*前一个元素的地址*/
}