最近想编写diyDB,所以想借鉴一下libevent的事件机制。之前也看过libevent的部分代码,但长时间不用已经忘的干净了。今天借此机会温习一下libevent,在此写个总结。
一、简介
libevent的最大看点是事件驱动和跨平台,当然还有轻量级。但是,其核心绝对是事件驱动机制。libevent的事件分为I/O事件,定时器事件和信号事件。
二、使用的简单介绍
下面是libevent的一个定时器事件的应用
1)首先初始化 libevent 库,并保存返回的指针
struct event_base * base = event_init();
实际上这一步相当于初始化一个 Reactor 实例;在初始化 libevent 后,就可以注册事件了。注意:在支持epoll的系统中每一个 Reactor 实例归根到底管理着一个epoll_wait().
2)初始化事件 event,设置回调函数和关注的事件
evtimer_set(&ev, timer_cb, NULL);
事实上这等价于调用 event_set(&ev, -1, 0, timer_cb, NULL);
event_set 的函数原型是:
void event_set(struct event *ev, int fd, short event, void (*cb)(int,
short, void *), void *arg)
ev:执行要初始化的 event 对象;
fd:该 event 绑定的“句柄”,对于信号事件,它就是关注的信号;
event:在该 fd 上关注的事件类型,它可以是 EV_READ, EV_WRITE, EV_SIGNAL;
cb:这是一个函数指针,当 fd 上的事件 event 发生时,调用该函数执行处理,它有三个参数,
调用时由 event_base 负责传入,按顺序,实际上就是 event_set 时的 fd, event 和 arg;
arg:传递给 cb 函数指针的参数;
由于定时事件不需要 fd,并且定时事件是根据添加时(event_add)的超时值设定的,因此
这里 event 也不需要设置。
这一步相当于初始化一个 event handler,在 libevent 中事件类型保存在 event 结构体中。
注意:libevent 并不会管理 event 事件集合,这需要应用程序自行管理;
3)设置 event 从属的 event_base
event_base_set(base, &ev);
这一步相当于指明 event 要注册到哪个 event_base 实例上;实际上就是指定注册到哪个epoll的事件注册表上
4)是正式的添加事件的时候了
event_add(&ev, timeout);
基本信息都已设置完成,只要简单的调用 event_add()函数即可完成,其中 timeout 是定时值;这一步相当于调用
Reactor::register_handler()函数注册事件。这里就是正式地将事件ev注册到对应的事件注册表上
5)程序进入无限循环,等待就绪事件并执行事件处理
event_base_dispatch(base);//核心就是while(){epoll_wait()};
三、事件处理流程的简单介绍
1)首先应用程序准备并初始化 event,设置好事件类型和回调函数;
2)向 libevent 添加该事件 event。对于定时事件,libevent 使用一个小根堆管理,key 为超
时时间;对于 Signal 和 I/O 事件,libevent 将其放入到等待链表(wait list)中,这是一
个双向链表结构;
3)程序调用 event_base_dispatch()系列函数进入无限循环,等待事件,以 select()函数为例;
每次循环前 libevent 会检查定时事件的最小超时时间 tv,根据 tv 设置 epoll()的最大等
待时间,以便于后面及时处理超时事件;
当 epoll()返回后,首先检查超时事件,然后检查 I/O 事件;
Libevent 将所有的就绪事件,放入到激活链表中;
然后对激活链表中的事件,调用事件的回调函数执行事件处理;
三、reactor模式
说起libevent模式就不得不说reactor模式,因为libevent的事件处理机制就是reactor模式
1.概念
Reactor 释义“反应堆”,是一种事件驱动机制。先向Reactor注册事件和回调函数,然后等事件发生时,reactor自动去调用对应的回调函数去处理事件(I/O 读写、定时和信号)。(注:通函数调用的是:应用程序不是主动的调用某个 API 完成处理)
2.优点
Reactor 模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:
1)响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;(回调函数中的操作最好是非阻塞的)
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
4)可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
3.reactor框架
使用 Reactor 模型,必备的几个组件:事件源、Reactor 框架、多路复用机制和事件处理程序
1) 事件源
Linux 上是文件描述符,Windows 上就是 Socket 或者 Handle 了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如 I/O 事件。
2) event demultiplexer——事件多路分发机制
由操作系统提供的 I/O 多路复用机制,比如 select 和 epoll。程序首先将其关心的句柄(事件源)及其事件注册到 event demultiplexer 上;当有事件到达时,event demultiplexer 会发出通知“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。对应到 libevent 中,依然是 select、poll、epoll 等,但是 libevent 使用结构体 eventop 进行了封装,以统一的接口来支持这些
I/O 多路复用机制,达到了对外隐藏底层系统机制的目的。
3) Reactor——反应器
Reactor,是事件管理的接口,内部使用 event demultiplexer 注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。对应到 libevent 中,就是 event_base 结构体。
4) Event Handler——事件处理程序
事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供 Reactor 在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。对应到 libevent 中,就是 event 结构体
四、事件event
Libevent 是基于事件驱动(event-driven)的,从名字也可以看到 event 是整个库的核心。event 就是 Reactor 框架中的事件处理程序组件;它提供了函数接口,供 Reactor 在事件发生时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄。事实上,这里的event已经包括了事件源和事件处理函数的信息
struct event { TAILQ_ENTRY (event) ev_next;//双向链表节点的指针,表示该事件在i/o事件的已注册事件链表中的位置 TAILQ_ENTRY (event) ev_active_next;//表示该事件在激活事件的已注册事件链表中的位置,如果挂上了,说明这个事件已经发生了,正在等待相应的事件处理函数被执行 TAILQ_ENTRY (event) ev_signal_next;//双向链表节点的指针,表示该事件在信号事件的已注册事件链表中的位置 unsigned int min_heap_idx; //如果该事件是超时事件,min_heap_idx表示该事件在超时事件小顶堆中的索引 struct event_base *ev_base;//ev_base 该事件所属的反应堆实例,这是一个 event_base 结构体 int ev_fd;//ev_fd,对于 I/O 事件,是绑定的文件描述符;对于 signal 事件,是绑定的信号(实际上是有名管道描述符); short ev_events;//event关注的事件类型 short ev_ncalls;//事件就绪执行时,调用 ev_callback 的次数,通常为 1; short *ev_pncalls;//指针,通常指向 ev_ncalls 或者为 NULL /* Allows deletes in callback */ struct timeval ev_timeout;//如果该事件是超时事件,ev_timeout表示超时事件的超市值 int ev_pri; /* smaller numbers are higher priority */ void (*ev_callback)(int, short, void *arg);//event 的回调函数,被 ev_base 调用,执行事件处理程序 void *ev_arg; int ev_res;//记录了当前激活事件的类型; /* result passed to event callback */ int ev_flags; }; 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/*永久事件,即每次事件发生并且处理完后,自动加入到相应队列中*/ 2)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 // event 已被初始化
event的管理流程解析:
每次当有事件 event 转变为就绪状态时, libevent 就会把它移入到 active event list[priority](就绪事件有优先级)中,其中 priority 是 event 的优先级;接着 libevent 会根据自己的调度策略选择就绪事件,调用其 cb_callback()函数执行事件处理;并根据就绪的句柄和事件类型填充 cb_callback 函数的参数。
在向libevent添加一个事件前必须先初始化一个事件,即设置事件。这通过调用 libevent 提供的函数有:event_set(), event_base_set(), event_priority_set()来完成。注:libevent 有一个全局 event_base 指针 current_base,默认情况下事件 ev将被注册到 current_base 上,使用该函数可以指定不同的 event_base;如果一个进程中存在多个libevent 实例,则必须要调用该函数为 event
设置不同的 event_base;