学习笔记:高性能IO框架库Libevent(一):概述

783 阅读6分钟

序: Linux服务器必须处理三类事件:IO事件、信号和定时事件。在处理三类事件时,通常需要考虑如下三个问题:

  1. 统一事件源,利用IO复用系统调用来管理所有事件。
  2. 可移植性,不同操作系统有不同的IO复用方式。
  3. 对并发编程的支持,在多进程和多线程环境下,需要考虑各执行体如何协同处理客户连接、信号和定时器,以避免竞态条件。

1. IO框架库概述

各个框架库实现原理基本类似,要么是Reactor模式实现,要么是Proactor模式实现,要么两种都实现。举例来说,基于Reactor模式实现的IO框架包括以下几个组件:句柄(handle)、事件多路分发器(EventDemultiplexer)、事件处理器(EventHandler)和具体的事件处理器(ConcreteEventHandler)、Reactor。

image.png

下面对这些组件分别进行解释:

  1. 句柄: IO框架库要处理的对象,即IO事件、信号和定时事件,统一称为事件源。一个事件源通常和句柄绑定在一起。但内核检测到就绪事件时,它将通过句柄来通知应用程序这件事。在linux环境下,IO事件对应的句柄是文件描述符,信号事件对应的就是信号值。
  2. 事件多路分发器: 事件的到来是随机的、异步的。我们无法预知程序合何时收到一个客户的连接请求,又亦或收到一个暂停信号。所以程序需要循环地等待并处理事件,这就是事件循环。在事件循环中,等待事件一般使用IO复用技术来实现。IO框架库一般将系统支持的各种IO复用系统调用封装成统一的接口,称为事件多路分发器。事件多路分发器的demultiplex方法是等待事件的核心函数,其内部调用的是select、poll、epoll_wait等函数。
  3. 事件处理器和具体事件处理器: 事件处理器执行事件对应的业务逻辑。它通常包含一个或者多个handle_event回调函数,这些回调函数在事件循环中被执行。事件处理器在框架中往往以接口的形式出现,使用时需要继承这个接口来实现具体的事件处理,保证可扩展性。
  4. Reator: Reator是IO框架库的核心。它提供了以下几个主要方法:
  • handle_events():执行事件循环。
  • register_handler():向多路分发器中注册事件。
  • remove_handler():删除多路分发器中的一个事件。

image.png

2. Libevent事件流程

当应用程序向libevent注册一个事件后,事件处理流程如下:

  1. 首先应用程序准备并初始化event,设置好事件类型和回调函数;
  2. 向libevent添加该事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于signal和IO事件,libevent将其放入到等待链表中(双向链表)。 3.程序调用event_base_dispatch()系列函数进入循环等待事件,以select()函数为例,每次循环前libevent会检查定时事件的最小超时时间tv,根据tv设置select()的最大等待事件,以便于后面及时处理超时事件;当select()返回后,首先检查超时事件,再检查IO事件;将就绪事件放入激活链表中;然后对激活链表中的事件,调用回调函数执行事件处理。

image.png

3. Libevent参考手册解读

1.创建event_base上下文

在使用libevent的功能前,需要创建一个或者多个event_base上下文。每个event_base上下文跟踪一系列事件并反馈哪些事件被触发。(默认event_base使用locking,保证多线程安全)

event_base目前支持以下IO复用方法:

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32

对应源码:

/* Array of backends in order of preference. */
static const struct eventop *eventops[] = {
#ifdef EVENT__HAVE_EVENT_PORTS
	&evportops,
#endif
#ifdef EVENT__HAVE_WORKING_KQUEUE
	&kqops,
#endif
#ifdef EVENT__HAVE_EPOLL
	&epollops,
#endif
#ifdef EVENT__HAVE_DEVPOLL
	&devpollops,
#endif
#ifdef EVENT__HAVE_POLL
	&pollops,
#endif
#ifdef EVENT__HAVE_SELECT
	&selectops,
#endif
#ifdef _WIN32
	&win32ops,
#endif
	NULL
};

tips:可以用event_config_avoid_method()来关闭上述任一方法。

对于大部分程序,新建一个默认的上下文即可

struct event_base *event_base_new(void);

该函数在"event2/event.h"中声明。

配置event_base:

struct event_config *event_config_new(void); \\新建配置上下文
struct event_base *event_base_new_with_config(const struct event_config *cfg);\\新建一个自定义配置的event_base
void event_config_free(struct event_config *cfg);\\释放配置上下文空间

event_config配置:

int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {
    EV_FEATURE_ET = 0x01, \\边缘触发
    EV_FEATURE_O1 = 0x02, \\增加删除事件的复杂度O(1)
    EV_FEATURE_FDS = 0x04, \\让libevent同时支持文件读写
};
int event_config_require_features(struct event_config *cfg,
                                  enum event_method_feature feature);

enum event_base_config_flag {
    EVENT_BASE_FLAG_NOLOCK = 0x01, \\单线程程序event_base无锁
    EVENT_BASE_FLAG_IGNORE_ENV = 0x02, \\忽略环境变量
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04, \\windows系统使能IOCP
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08, \\每次事件回调都检查系统时间,增加CPU负担
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,\\开启可以避免同一事件句柄在多个事件同时发生是多次触发,只支持epoll模型。
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20 \\提供更细粒度定时
};
int event_config_set_flag(struct event_config *cfg,
    enum event_base_config_flag flag);

其他配置: 在window下制定可用cpu数

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)

限制低优先级事件的回调处理函数的最大事件间隔和最大处理事件数,以防优先级翻转。

int event_config_set_max_dispatch_interval(struct event_config *cfg,
    const struct timeval *max_interval, int max_callbacks,
    int min_priority);

示例代码:

\\Example: Preferring edge-triggered backends
struct event_config *cfg;
struct event_base *base;
int i;

/* My program wants to use edge-triggered events if at all possible.  So
   I'll try to get a base twice: Once insisting on edge-triggered IO, and
   once not. */
for (i=0; i<2; ++i) {
    cfg = event_config_new();

    /* I don't like select. */
    event_config_avoid_method(cfg, "select");

    if (i == 0)
        event_config_require_features(cfg, EV_FEATURE_ET);

    base = event_base_new_with_config(cfg);
    event_config_free(cfg);
    if (base)
        break;

    /* If we get here, event_base_new_with_config() returned NULL.  If
       this is the first time around the loop, we'll try again without
       setting EV_FEATURE_ET.  If this is the second time around the
       loop, we'll give up. */
}
Example: Avoiding priority-inversion
struct event_config *cfg;
struct event_base *base;

cfg = event_config_new();
if (!cfg)
   /* Handle error */;

/* I'm going to have events running at two priorities.  I expect that
   some of my priority-1 events are going to have pretty slow callbacks,
   so I don't want more than 100 msec to elapse (or 5 callbacks) before
   checking for priority-0 events. */
struct timeval msec_100 = { 0, 100*1000 };
event_config_set_max_dispatch_interval(cfg, &msec_100, 5, 1);

base = event_base_new_with_config(cfg);
if (!base)
   /* Handle error */;

event_base_priority_init(base, 2);

检查event_base支持的io复用方法:

const char **event_get_supported_methods(void);

tips:数组最后一个元素为NULL

示例代码:

int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s.  Available methods are:\n",
    event_get_version());
for (i=0; methods[i] != NULL; ++i) {
    printf("    %s\n", methods[i]);
}

检查当前使用的ui复用方法以及支持的features:

struct event_base *base;
enum event_method_feature f;

base = event_base_new();
if (!base) {
    puts("Couldn't get an event_base!");
} else {
    printf("Using Libevent with backend method %s.",
        event_base_get_method(base));
    f = event_base_get_features(base);
    if ((f & EV_FEATURE_ET))
        printf("  Edge-triggered events are supported.");
    if ((f & EV_FEATURE_O1))
        printf("  O(1) event notification is supported.");
    if ((f & EV_FEATURE_FDS))
        printf("  All FD types are supported.");
    puts("");
}

释放event_base空间:

void event_base_free(struct event_base *base);

设置事件优先级等级:(默认所有事件为同一等级)

int event_base_priority_init(struct event_base *base, int n_priorities);

在进程fork()后重新初始化event_base():

int event_reinit(struct event_base *base);

示例代码:

Example
struct event_base *base = event_base_new();

/* ... add some events to the event_base ... */

if (fork()) {
    /* In parent */
    continue_running_parent(base); /*...*/
} else {
    /* In child */
    event_reinit(base);
    continue_running_child(base); /*...*/
}