Libevent是开源社区一款高性能的I/O框架库,具有如下特点:
- 跨平台支持。Libevent支持Linux、Unix、Windows
- 统一事件源。Libevent对I/O事件、信号、定时事件提供统一的处理。
- 线程安全。Libevent使用libevent_pthreads库提供线程安全支持。
- 基于Reactor模式实现。
12.1 I/O框架库概述
各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Proactor模式实现,要么同时以这两种模式实现。
以Reactor模式为例,Reactor模式的I/O框架包含如下几个组件:
- 句柄(Handle)
- 事件多路分发器(EventDemultiplexer)
- 事件处理器(EventHandler)
- 具体事件处理器(ConcreteEventHandler)
- Reactor
这些组件的关系如下图所示:
(1)句柄
句柄的作用是,当内核检测到就绪事件时,他将通过句柄来通知应用程序这一事件。在Linux环境下,I/O事件对应的句柄时文件描述符,信号对应的句柄是信号值。
(2)事件多路分发器
事件的到来是随机的、异步的,所以程序要循环等待并处理事件。在循环中,等待事件一般使用I/O复用技术来实现。I/O框架库将各种I/O复用系统调用封装成统一的接口,称为事件多路分发器。事件分发器的demultiplex方法是等待事件的核心函数,其内部调用的是select、poll、epoll_wait等函数。此外,事件分发器还需要实现register_event和remove_event方法,以供调用者往事件分发器中添加事件和删除事件。
(3)事件处理器和具体事件处理器
事件处理器执行事件对应的业务逻辑。它通常包含一个或多个handle_event回调函数,这些回调函数在事件循环中被执行。事件处理器一般还提供一个get_handle方法,它返回与该事件处理器关联的句柄。当事件分发器检测到有事件发生时,会通过句柄通知应用程序。因此,必须将事件处理器和句柄绑定。
事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器。因此事件处理器中的回调函数一般是虚函数。
(4)Reactor
Reactor时I/O框架库的核心。它提供一下几个主要方法:
- handle_events:执行事件循环,即:循环等待事件,然后一次处理所有就绪事件对应的事件处理器。
- register_handler:调用事件分发器的register_event方法来向事件分发器中注册一个事件。
- remove_handler:调用事件分发器的remove_event方法来删除事件分发器中的一个事件。
下图总结了I/O框架库的工作时序:
12.2 Libevent源码分析
12.2.1 一个实例
使用Libevent库实现一个“Hello World”程序。
#include <sys/signal.h>
#include <event.h>
void signal_cb(int fd, short event, void* argc)
{
struct event_base* base = (event_base*)argc;
struct timeval delay = {2, 0};
printf("caught an interrupt signal, exiting in two seconds\n");
event_base_loopexit(base, &delay);
}
void timeout_cb(int fd, short event, void* argc)
{
printf("timeout\n");
}
int main()
{
struct event_base* base = event_init();
struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, base);
event_add(signal_event, NULL);
timeval tv = {1, 0};
struct event* timeout_event = evtimer_new(base, timeout_cb, NULL);
event_add(timeout_event, &tv);
event_base_dispatch(base);
event_free(timeout_event);
event_free(signal_event);
event_base_free(base);
return 0;
}
上面代码描述出了Libevent库的主要逻辑:
(1)使用event_init函数创建event_base对象。一个event_base相当于一个Reactor实例。
(2)创建具体的事件处理器,并设置它们所从属的Reactor实例。evsignal_new和evtimer_new分别用于创建信号事件处理器和定时事件处理器,它们时event.h文件中的宏:
#define evsignal_new(b,x,cb,arg) event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evtimer_new(b,cb,arg) event_new((b), -1, 0, (cb), (arg))
可见,它们的统一入口是event_new函数,即:用于创建通用事件处理器(EventHandler)的函数,其定义是:
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg);
参数解释:
base:指定新创建的事件处理器从属的Reactor
fd:指定与该事件处理器关联的句柄。
(创建I/O事件处理器时,应该给fd参数传递文件描述符值;创建信号事件处理器时,应该给fd参数传递信号值;创建定时器事件时,应该给fd参数传递-1)
events:指定事件类型,可选值如下:
#define EV_TIMEOUT 0x01 //定时事件
#define EV_READ 0x02 //可读事件
#define EV_WRITE 0x04 //可写事件
#define EV_SIGNAL 0x08 //信号事件
#define EV_PERSIST 0x10 //永久事件
#define EV_ET 0x20 //边缘触发事件
cb:指定事件对应的回调函数。相当于事件处理器中的handle_event方法。
arg:Reactor传递给回调函数的参数
返回值:
成功时返回一个event类型的对象,也就是Libevent的事件处理器。
注:libevent用event来描述事件处理器,而不是事件。
为了区分事件和事件处理器,做如下约定:
- 事件指一个句柄上绑定的事件。如文件描述符上的可读事件
- 事件处理器,即event结构体对象,包含句柄和事件类型,及回调函数等。
- 事件由事件多路分发器管理;事件处理器由事件队列管理。
- 事件循环对一个就绪事件的处理,指的是执行该事件对应的事件处理器中的回调函数。
(3)使用event_add函数将事件处理器添加到注册事件队列中,并将该事件处理器对应的事件添加到事件分发器中。event_add相当鱼Reactor中的register_handler函数。
(4)使用event_base_dispatch函数执行事件循环。
(5)事件循环结束后,使用*_free系列函数释放系统资源。