Linux服务器程序必须处理的三类事件:

在处理这三类事件时我们通常需要考虑如下三个问题:

幸运的是,开源社区提供了很多优秀的I/O框架库,他们不仅解决了上述问题,让开发者可以将精力完全放在程序的逻辑上,而且稳定性、性能等各方面都相当出色。而Libevent就是其中相对轻量级的框架库。

I/O框架库概述

I/O框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组更便于使用的接口。这些库函数往往比程序员自己实现的同样功能的函数更合理、更高效、且更健壮。因为它们经受住了真实网络环境下的高压测试,以及时间的考验。

各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Procator模式实现(高性能服务器程序框架 - 两种高效的事件处理模式),要么同时以这两种模式实现。举例来说,基于Reactor模式的I/O框架库包含如下几个组件:

这些组件关系如下图:

libevent开源库(高性能IO框架库Libevent-)(1)

  1. 句柄: I/O 框架库要处理的对象,即I/O事件、信号和定时事件,统一称为事件源。一个事件源通常和一个句柄绑定在一起。句柄的作用是,当内核检测到就绪事件时,它将通过句柄来通知应用程序这一事件。在Linux环境下,I/O事件对应的句柄是文件描述符,信号事件对应的句柄就是信号值。
  2. 事件多路分发器:事件的到来是随机的、异步的。我们无法预知程序何时收到一个客户连接请求,又亦活收到一个暂停信号。所以程序需要循环地等待并处理事件,这就是事件循环。在事件循环中,等待事件一般使用I/O复用技术来实现。I/O框架库一般将系统支持的各种I/O复用系统调用封装成统一的接口,称为事件多路分发器。事件多路分发器的demultiplex方法是等待事件的核心函数,其内部调用的是select、poll、epoll_wait等函数。此外事件多路分发器还需实现register_event和remove_event方法,以供调用者往事件多路分发器中添加事件和从事件多路分发器中删除事件。
  3. 事件处理器和具体时间处理器:事件处理器执行事件对应的业务逻辑。它通常包含一个或多个handle_event回调函数,这些回调函数在事件循环中被执行。I/O框架库提供的事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器。因此,事件处理器中的回调函数一般被声明为需函数,以支持用户的扩展。此外,事件处理器一般还提供一个get_handle方法,它返回与该事件处理器关联的句柄。那么事件处理器和句柄有什么关系?当时间多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的。因此,我们必须将事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器。
  4. Reactor:Reactor是I/O框架的核心。它提供的几个主要方法是:
    1. handle_events:该方法执行事件循环。它重复如下过程:等待事件,然后依次处理所有就绪事件对应的事件处理器。
    2. register_handler:该方法调用事件多路分发器的register_event方法来往事件多路分发器中注册一个事件。
    3. remove_handler:该方法调用事件多路分发器的remove_event方法来往删除事件多路分发器中注册一个事件。

I/O框架库的工作时序如下:

libevent开源库(高性能IO框架库Libevent-)(2)

Libevent源码分析

Libevent是开源社区的一款高性能的I/O框架库,具有如下特点:


一个实例

下面是用Libevent库实现的一个“Hello World”程序。

#include <sys/signal.h> #include <event2/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 cleanly in two seconds....\n"); event_base_loopexit(base, &delay); } void timeout_cb(int fd, short event, void* argc) { printf("timeout\n"); } int main(int argc, char const *argv[]) { struct event_base* base = event_base_new(); 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_base_new函数创建event_base对象。一个event_base相当于一个Reactor实例。
  2. 创建具体的事件处理器,并设置它们所从属的Reactor实例。evsignal_new和evtimer_new分别用于创建信号事务处理器和定时事件处理器。它们是定义在如下:

libevent开源库(高性能IO框架库Libevent-)(3)

#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函数,即用于创建通用事件处理器的函数,定义如下:

event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)其中,base参数指定行

其中:

#define EV_TIMEOUT 0x01 /*定时事件*/ #define EV_READ 0x02 /*可读事件*/ #define EV_WRITE 0x04 /*可写事件*/ #define EV_SIGNAL 0x08 /*信号事件*/ #define EV_PERSIST 0x10 /*永久事件*/ /*边缘触发事件,需要I/O复用系统调用支持,比如epoll */ #define EV_ET 0x20

上述代码中,EV_PERSIST的作用是:事件被触发后,自动重新对这个event调用event_add函数。

event_new函数成功时返回一个event类型的对象,也就是Libevent的事件处理器。Libevent用单词“event”来描述事件处理器,而不是事件,所以约定如下:


  1. 调用event_add函数,将事件处理器添加到注册事件队列中,并将该事件处理器对应的事件添加到事件多路分发器中。even_add函数相当于Reactor中的register_handler方法。
  2. 调用event_base_dispatch函数来执行事件循环
  3. 事件循环结束后,使用*_free系列释放系统资源
源代码组织结构

在整个源码中,event-internal.h、include/event2/event_struct.h、event.c和evmap.c等4个文件最为重要。它们定义了event和event_base结构体,并实现了这两个结构体的相关操作。

,