Jason Pan

libevent 学习笔记 02

潘忠显 / 2020-07-16


本文主要是Libevent概览,重点介绍了库中两个最重要的结构event_baseevent,也提及到一些为了方便而做的封装。

〇、Libevent概览

设计目标

组件

不同的Libevent的头文件对应到不同的组件,主要的几个组件有:

组件名称 组件描述
evutil 通用功能,抽象出不同平台的网络实现之间的差异
event/
event_base
核心功能,基于不同平台的事件非阻塞IO后端提供了统一的抽象API
bufferevent 对event的封装,缓冲套接字的读写,不需要在套接字就绪时立马处理
evbuffer 该模块实现了底层缓冲事件的缓冲,并提供了方便有效访问的功能
evhttp HTTP客户端/服务器的简单实现
evdns DNS客户端/服务器的简单实现
evrpc RPC的简单实现

构建Libevent时候,会产生几个库。 如果是直接通过configure + make构建,可以在.lib目录中找到。 如果 使用Bazel构建 时,会在bazel-bin/copy_libevent/libevent/lib/目录中找到。

这几个库的名称以及对应的含义:

库名 包含的内容
libevent_core 所有核心事件和缓冲区功能。该库包含所有event_baseevbufferbuffereventevent_util
libevent_extra 扩展的协议功能,包括HTTP、DNS和RPC
libevent 包含libevent_core和libevent_extra的内容,后边可能被弃用
libevent_pthreads 基于pthreads线程库添加了线程和锁定实现,使用多线程方式的Libevent时需要链接该库
libevent_openssl 提供对使用bufferevents和OpenSSL库的加密通信的支持,使用加密连接需要链接这个库

一、event_base 结构

使用Libevent的函数之前,需要分配若干个 event_base 结构,每个 event_base 维护着一个事件集合(a set of events and can poll to determine which events are active)。

如果event_base设置了使用锁,可以用于多线程。

event_base 会使用“一种方法”来判断哪些事件是否ready。这些方法主要对应不同平台上最高效的异步IO通知方法,包括:select/poll/epoll/kqueue/devpoll/evport/IOCP

默认创建

#include <event2/event.h>
struct event_base *event_base_new(void); 

event_base_new()会检查环境变量,自动选择系统中最快的事件监听方法,创建event_base结构。

可以指定不使用的方法的两种方式:

另外还有一些辅助函数可以获得一些关于event_base的信息:

自定义创建

通过创建并设置 event_config,再传递给函数 event_base_new_with_config(),来控制创建何种 event_base。

event_config_new / event_base_new_with_config / event_config_free 

通过event_config_set_max_dispatch_interval()设置检查新事件的时间间隔。

销毁

使用 event_base_free() 可以销毁一个 event_base,但是不会销毁维护的事件,不会关闭连接,也不会释放指针

优先级

优先级是event的属性。但是因为事件都是在event_base维护,因此event_base的优先级范围决定了其中事件可以设置什么优先级。

默认情况下,一个event_base只支持一个优先级。调用 event_base_priority_init 函数,可以让一个event_base支持的优先级范围从 0 到 N-10是最重要的,N - 1为最不重要的。

事件默认的优先级是 N / 2,创建之后可以通过 event_priority_set() 设置。

使用 event_base_get_npriorities(base) 获得 event_base 支持的优先级总数N

fork()后需再初始化

通过fork()创建子进程后,如果还需要使用event_base,则需要调用 event_reinit() 重新初始化。

debug信息

event_base_dump_events() 函数可以导出event_base中添加的所有事件及状态,写入到对应的FILE中。

遍历所有的事件并运行一个函数

event_base_foreach_event() 函数可以给event_base中的每个事件都运行一个函数,类似于std::for_each()函数

二、event 结构

表示的发生条件

event是Libevent操作的基础单元,每个event代表着以下条件发生:

创建与销毁

使用event_new()创建事件,使用event_free()销毁事件。

typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

struct event *event_new(     //
    struct event_base *base, // 需要使用base来分配和构造event
    evutil_socket_t fd,      // 非0表示要监控的fd
    short what,              // 监控fd的事件类型(Flags)
    event_callback_fn cb,    // 事件变成active后需要运行的回调函数
    void *arg                // 回调函数的*其他*参数
);

void event_free(struct event *event);

Flags (参数what的值)

#define EV_TIMEOUT      0x01
#define EV_READ         0x02
#define EV_WRITE        0x04
#define EV_SIGNAL       0x08
#define EV_PERSIST      0x10
#define EV_ET           0x20

上边这种以位来标识实际含义的宏定义方式,通常都是为了几种flag模式可以使用按位或 |

EV_PERSIT 标识持久事件(persistent),当active状态调用cb之后,又变成pending状态,而不是non-pending状态。

事件的状态转移

三种状态

状态转移图

持久事件的状态转移

If you have an event with flags EV_READ|EV_PERSIST and a timeout of five seconds, the event will become active:

创建特殊事件

为了方便,libevent定义了许多宏,用来处理特殊的事件:

使用这些宏的好处,可以简化开发,也能使代码更清晰、可读性更好。

#define evtimer_new(base, callback, arg) \
    event_new((base), -1, 0, (callback), (arg))
#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)

Note that signal callbacks are run in the event loop after the signal occurs, so it is safe for them to call functions that you are not supposed to call from a regular POSIX signal handler.

一次性事件可以通过调用event_base_once()函数创建,与event_new()类似

使用事件本身作为callback参数

上边event_new()的定义中的arg是用于当事件变成active时,传递给callback的参数。

调用event_self_cbarg()可以得到神奇的事件本身的指针,会告诉event_new()将自己作为CB的参数传入。换句话说:将event_new()的输出作为自己的输入。

用途举例:在CB中销毁自己。

事件优先级

多个优先级的多个事件同时变为active时:

  1. Libevent运行高优先级事件(callback),然后再次检查事件;
  2. 仅当没有active的高优先级事件时,低优先级事件才会运行。

在介绍 event_base 的时候有提到,一个event_base是可以设置优先级范围,针对一个事件则可以统调用 event_priority_set() 函数,来给一个事件指定一个在event_base优先级范围内的优先级。

event_base_priority_init(base, 2); 
/* Now base has priority 0, and priority 1 */
struct event* important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); 
struct event* unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
event_priority_set(important, 0);
event_priority_set(unimportant, 1)

获取事件属性

event_get_ 开头的函数,可以获得事件属性。

上边有提到event_get_base()可以根据事件找到base,而event_base_get_running_event()可以根据base找到其维护事件。

struct event *event_base_get_running_event(struct event_base *base);

其他

三、循环

event_base 循环

event_base_loop 就相当于一个for循环,循环等待事件发生并调用其callback。直到所有的事件都处理完成/无监听的事件,或通过event_base_loopexit()等方式显式退出。


int event_base_loop(struct event_base *base, int flags); 

如果什么flags都不设置,即flags=0的情况下, 循环过程中重复检查是否有注册的事件被触发,就回将时间变成active状态,调用其回调函数。 循环的event_base中没有注册事件了,就停止,继续往下走。

特殊标签

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04

event_base_dispatch

大部分情况下,会使用到flags=0设置的event_base_loop(),为了方便,直接定义了一个event_base_dispatch()

// 是直接 define 定义的吗?

exit/break/continue

通常是检查所有事件,然后运行其中激活事件中具有最高优先级的事件,然后再重复这一过程。

时间缓存