Book: Programming with Libevent(2)--A Libevent Reference Manual(1)

11 篇文章 1 订阅
6 篇文章 0 订阅

A Libevent Reference Manual

参考链接

Fast portable non-blocking network programming with Libevent

R0: Preliminaries

Libevent from 10,000 feet

Libevent 是一个用于编写快速可移植非阻塞 IO 的库。其设计目标是:

  • 可移植性
    使用 Libevent 编写的程序应该可以在 Libevent 支持的所有平台上运行。即使没有真正好的 方法来进行非阻塞 IO,Libevent 也应该支持马马虎虎的方式,以便您的程序可以在受限制的环境中运行。

  • 速度
    Libevent 尝试在每个平台上使用最快的可用非阻塞 IO 实现,并且不会像这样做那样引入太多开销。

  • 可扩展性
    Libevent 设计为即使在需要具有数万个活动套接字的程序中也能很好地工作

  • 方便
    只要有可能,使用 Libevent 编写程序的最自然方式应该是稳定、可移植的方式

libevent分为以下几个组件:

  • 实用程序
    抽象出不同平台网络实现之间差异的通用功能。

  • eventevent_base
    这是 Libevent 的核心。它为各种特定于平台的、基于事件的非阻塞 IO 后端提供抽象 API。它可以让您知道套接字何时准备好读取或写入、执行基本超时功能以及检测操作系统信号

  • 缓冲事件
    这些函数为 Libevent 基于事件的核心提供了一个更方便的包装器。它们让您的应用程序请求缓冲读取和写入,而不是在套接字准备就绪时通知您,而是让您知道 IO 何时实际发生
    bufferevent 接口还有多个后端,因此它可以利用提供更快方法来执行非阻塞 IO 的系统,例如 Windows IOCP API

  • 缓冲区
    该模块实现了缓冲事件底层的缓冲区,并提供了高效和/或方便访问的功能。

  • evhttp
    一个简单的 HTTP 客户端/服务器实现

  • evdns
    一个简单的 DNS 客户端/服务器实现

  • 虚拟主机
    一个简单的 RPC 实现。

The Libraries

构建 Libevent 时,默认情况下它会安装以下库:

  • libevent_core
    所有核心事件和缓冲区功能。该库包含所有 event_baseevbufferbufferevent 和实用程序函数。

  • libevent_extra
    该库定义了您的应用程序可能需要也可能不需要的特定于协议的功能,包括 HTTPDNSRPC

  • 自由事件
    这个library的存在是出于历史原因;它包含 libevent_corelibevent_extra 的内容。你不应该使用它;它可能会在未来版本的 Libevent 中消失。

以下库仅安装在某些平台上:

  • libevent_pthreads
    该库基于 pthreads 可移植线程库添加了线程和锁定实现。它与 libevent_core 分开,因此您不需要链接 pthreads 来使用 Libevent,除非您实际上以多线程方式使用 Libevent。

  • libevent_openssl
    该库提供对使用 buffereventsOpenSSL 库的加密通信的支持。它与 libevent_core 分开,因此除非您实际使用加密连接,否则您无需链接 OpenSSL 即可使用 Libevent 。

The Headers

当前所有公共 Libevent 头文件都安装在event2 目录下。headers分为三大类:

  • API 标头
    API 标头是定义 Libevent 的当前公共接口的标头。这些标题没有特殊的后缀。

  • 兼容性标头
    兼容性标头包括已弃用函数的定义。除非您从旧版本的 Libevent 移植程序,否则不应包含它。

  • 结构头
    这些头文件定义了布局相对易变的结构。其中一些是公开的,以防您需要快速访问结构组件;有些是由于历史原因而暴露的。直接依赖头文件中的任何结构可能会破坏程序与其他版本的 Libevent 的二进制兼容性,有时以难以调试的方式。这些头文件的后缀是_struct.h

(也有没有event2目录的旧版本Libevent头 文件。请参阅下面的“如果您必须使用旧版本的 Libevent”。)

If you have to work with an old version of Libevent

Libevent 2.0 对其 API 进行了修改,使其总体上更加合理且不易出错。如果可能,您应该编写新程序来使用 Libevent 2.0 API。但有时您可能需要使用较旧的 API,以更新现有应用程序,或支持由于某种原因无法安装 Libevent 2.0 或更高版本的环境。

旧版本的 Libevent 具有较少的标头,并且没有将它们安装在event2下:

OLD HEADER……REPLACED BY CURRENT HEADERS
event.hevent2/event*.h, event2/buffer*.h event2/bufferevent*.h event2/tag*.h
evdns.hevent2/dns*.h
evhttp.hevent2/http*.h
evrpc.hevent2/rpc*.h
evutil.hevent2/util*.h

在 Libevent 2.0 及更高版本中,旧标头仍然作为新标头的包装器存在。

在 1.4 之前,只有一个库“libevent”包含当前拆分为 libevent_core 和 libevent_extra 的功能。

在 2.0 之前,不支持锁定;Libevent 可以是线程安全的,但前提是您确保永远不会同时使用来自两个线程的相同结构。

下面的各个部分将讨论您在代码库的特定区域可能会遇到的过时 API。

Notes on version status

1.4.7 之前的 Libevent 版本应该被认为是完全过时的。1.3e 左右之前的 Libevent 版本应该被认为是无可救药的 bug。

(另外,请不要向 Libevent 维护者发送 1.4.x 或更早版本的任何新功能——它应该保持稳定版本。如果您在 1.3x 或更早版本中遇到错误,请确保它仍然存在在您报告之前存在于最新的稳定版本中:后续版本的发生是有原因的。)

R1: Setting up the Libevent library

libevent 有一些在整个过程中共享的全局设置。这些会影响整个library。

在调用 Libevent 库的任何其他部分之前,您必须对这些设置进行任何更改。如果不这样做,Libevent 可能会处于不一致的状态。

Log messages in Libevent

libevent 可以记录内部错误和警告。如果编译时支持日志记录,它还会记录调试消息。默认情况下,这些消息被写入 stderr。您可以通过提供自己的日志记录功能来覆盖此行为。

interface

#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3

/* Deprecated; see note at the end of this section */
#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
#define _EVENT_LOG_MSG   EVENT_LOG_MSG
#define _EVENT_LOG_WARN  EVENT_LOG_WARN
#define _EVENT_LOG_ERR   EVENT_LOG_ERR

typedef void (*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb);

要覆盖 Libevent 的日志记录行为,请编写您自己的匹配 event_log_cb 签名的函数,并将其作为参数传递给 event_set_log_callback()。每当 Libevent 想要记录消息时,它都会将其传递给您提供的函数。您可以通过使用 NULL 作为参数再次调用 event_set_log_callback() 来让 Libevent 返回其默认行为。

example

#include <event2/event.h>
#include <stdio.h>

static void discard_cb(int severity, const char *msg)
{
    /* This callback does nothing. */
}

static FILE *logfile = NULL;
static void write_to_file_cb(int severity, const char *msg)
{
    const char *s;
    if (!logfile)
        return;
    switch (severity) {
        case _EVENT_LOG_DEBUG: s = "debug"; break;
        case _EVENT_LOG_MSG:   s = "msg";   break;
        case _EVENT_LOG_WARN:  s = "warn";  break;
        case _EVENT_LOG_ERR:   s = "error"; break;
        default:               s = "?";     break; /* never reached */
    }
    fprintf(logfile, "[%s] %s\n", s, msg);
}

/* Turn off all logging from Libevent. */
void suppress_logging(void)
{
    event_set_log_callback(discard_cb);
}

/* Redirect all Libevent log messages to the C stdio file 'f'. */
void set_logfile(FILE *f)
{
    logfile = f;
    event_set_log_callback(write_to_file_cb);
}

NOTE

从用户提供的 event_log_cb 回调中调用 Libevent 函数是不安全的!例如,如果您尝试编写使用 bufferevents 向网络套接字发送警告消息的日志回调,您可能会遇到奇怪且难以诊断的错误。在未来版本的 Libevent 中,某些功能可能会删除此限制。

通常,调试日志不会启用,也不会发送到日志回调。如果 Libevent 是为支持它们而构建的,您可以手动打开它们。

interface

#define EVENT_DBG_NONE 0 
#define EVENT_DBG_ALL 0xffffffffu void event_enable_debug_logging(ev_uint32_t which);

调试日志很冗长,在大多数情况下不一定有用。使用 EVENT_DBG_NONE 调用 event_enable_debug_logging() 获得默认行为;使用 EVENT_DBG_ALL 调用它会打开所有支持的调试日志。未来版本可能会支持更细粒度的选项。

这些函数在 <event2/event.h> 中声明。它们首次出现在 Libevent 1.0c 中,但 event_enable_debug_logging() 首次出现在 Libevent 2.1.1-alpha 中。

兼容性说明
在 Libevent 2.0.19-stable 之前,EVENT_LOG_* 宏的名称以下划线开头:_EVENT_LOG_DEBUG_EVENT_LOG_MSG_EVENT_LOG_WARN_EVENT_LOG_ERR。这些旧名称已弃用,仅应用于与 Libevent 2.0.18-stable 及更早版本的向后兼容。它们可能会在未来版本的 Libevent 中删除。

Handling fatal errors

当 Libevent 检测到**不可恢复的内部错误(例如损坏的数据结构)**时,其默认行为是调用 exit()abort() 以离开当前正在运行的进程。这些错误几乎总是意味着某处存在错误:要么在您的代码中,要么在 Libevent 本身中。

如果您希望应用程序更优雅地处理致命错误,您可以覆盖 Libevent 的行为,方法是提供一个 Libevent 应该调用的函数来代替退出

interface

typedef  void (*event_fatal_cb)( int err);
void event_set_fatal_callback(event_fatal_cb cb);

要使用这些函数,首先定义一个新函数,Libevent 在遇到致命错误时应调用该函数,然后将其传递给 event_set_fatal_callback()。稍后,如果 Libevent 遇到致命错误,它将调用您提供的函数。

您的函数不应将控制权返回给 Libevent;这样做可能会导致未定义的行为,并且 Libevent 可能会退出以避免崩溃。一旦您的函数被调用,您不应再调用任何其他 Libevent 函数。

这些函数在 <event2/event.h> 中声明。它们首先出现在 Libevent 2.0.3-alpha 中。

Memory management

默认情况下,Libevent 使用 C 库的内存管理函数从堆分配内存。您可以通过为 mallocreallocfree 提供您自己的替代品,让 Libevent 使用另一个内存管理器。如果您希望 Libevent 使用更高效的分配器,或者您希望 Libevent 使用检测的分配器来查找内存泄漏,则您可能想要这样做。

Interface

void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
                             void *(*realloc_fn)(void *ptr, size_t sz),
                             void (*free_fn)(void *ptr));

这是一个简单的例子,它用计算分配的字节总数的变体替换了 Libevent 的分配函数。实际上,当 Libevent 在多个线程中运行时,您可能希望在此处添加锁定以防止出现错误

Example


#include <event2/event.h>
#include <sys/types.h>
#include <stdlib.h>

/* This union's purpose is to be as big as the largest of all the
 * types it contains. */
union alignment {
    size_t sz;
    void *ptr;
    double dbl;
};
/* We need to make sure that everything we return is on the right
   alignment to hold anything, including a double. */
#define ALIGNMENT sizeof(union alignment)

/* We need to do this cast-to-char* trick on our pointers to adjust
   them; doing arithmetic on a void* is not standard. */
#define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
#define INPTR(ptr) (((char*)ptr)-ALIGNMENT)

static size_t total_allocated = 0;
static void *replacement_malloc(size_t sz)
{
    void *chunk = malloc(sz + ALIGNMENT);
    if (!chunk) return chunk;
    total_allocated += sz;
    *(size_t*)chunk = sz;
    return OUTPTR(chunk);
}
static void *replacement_realloc(void *ptr, size_t sz)
{
    size_t old_size = 0;
    if (ptr) {
        ptr = INPTR(ptr);
        old_size = *(size_t*)ptr;
    }
    ptr = realloc(ptr, sz + ALIGNMENT);
    if (!ptr)
        return NULL;
    *(size_t*)ptr = sz;
    total_allocated = total_allocated - old_size + sz;
    return OUTPTR(ptr);
}
static void replacement_free(void *ptr)
{
    ptr = INPTR(ptr);
    total_allocated -= *(size_t*)ptr;
    free(ptr);
}
void start_counting_bytes(void)
{
    event_set_mem_functions(replacement_malloc,
                            replacement_realloc,
                            replacement_free);
}

NOTES

替换内存管理函数会影响所有未来从 Libevent 分配、调整大小或释放内存的调用。因此,您需要确保在调用任何其他 Libevent 函数之前替换这些函数。否则,Libevent 将使用您的 free 版本来释放从 C 库版本的 malloc 返回的内存。

  • 您的 malloc 和 realloc 函数需要返回与 C 库具有相同对齐方式的内存块
  • 您的 realloc 函数需要正确处理 realloc(NULL, sz) (即,将其视为 malloc(sz))。
  • 您的 realloc 函数需要正确处理 realloc(ptr, 0) (即,将其视为 free(ptr))。
  • 您的 free 函数不需要处理 free(NULL)
  • 您的 malloc 函数不需要处理 malloc(0)
  • 如果您从多个线程使用 Libevent,则替换的内存管理函数需要是线程安全的。
  • Libevent 将使用这些函数来分配它返回给您的内存。因此,如果您想释放由 Libevent 函数分配和返回的内存,并且您已经替换了 malloc 和 realloc 函数,那么您可能必须使用替换的 free 函数来释放它。

event_set_mem_functions() 函数在 <event2/event.h> 中声明。它首次出现在 Libevent 2.0.1-alpha 中。

Libevent 可以在禁用 event_set_mem_functions() 的情况下构建。如果是,则使用 event_set_mem_functions 的程序将不会编译或链接。在 Libevent 2.0.2-alpha 及更高版本中,您可以通过检查是否定义了 EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED 宏来检测 event_set_mem_functions() 的存在。

Locks and threading

您可能知道,如果您正在编写多线程程序,同时从多个线程访问相同的数据并不总是安全的

libevent 结构通常可以通过多线程以三种方式工作。

  • 某些结构本质上是单线程的:同时从多个线程使用它们永远不会安全。
  • 某些结构是可选锁定的:您可以为每个对象告诉 Libevent 是否需要同时从多个线程使用它。
  • 一些结构总是被锁定:如果 Libevent 运行时支持锁定,那么它们总是可以安全地同时从多个线程使用。

要在 Libevent 中获得锁定,您必须告诉 Libevent 要使用哪些锁定函数。您需要在调用任何分配需要在线程之间共享的结构的 Libevent 函数之前执行此操作。

如果您正在使用 pthreads 库或本机 Windows 线程代码,那么您很幸运。有一些预定义的函数可以将 Libevent 设置为使用正确的 pthread 或 Windows 函数。

interface

#ifdef WIN32
 int evthread_use_windows_threads( void ); 
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED 
#endif 
#ifdef _EVENT_HAVE_PTHREADS int evthread_use_pthreads( void ); 
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 
#endif

这两个函数在成功时返回 0,在失败时返回 -1。

如果您需要使用不同的线程库,那么您还有更多的工作要做。您需要定义使用您的库来实现的函数:

  • Locks
  • locking
  • unlocking
  • lock allocation
  • lock destruction
  • Conditions
  • condition variable creation
  • condition variable destruction
  • waiting on a condition variable
  • signaling/broadcasting to a condition variable
  • Threads
  • thread ID detection

然后你将这些函数告诉 Libevent,使用 evthread_set_lock_callbacksevthread_set_id_callback 接口。

Interface

#define EVTHREAD_WRITE  0x04
#define EVTHREAD_READ   0x08
#define EVTHREAD_TRY    0x10

#define EVTHREAD_LOCKTYPE_RECURSIVE 1
#define EVTHREAD_LOCKTYPE_READWRITE 2

#define EVTHREAD_LOCK_API_VERSION 1

struct evthread_lock_callbacks {
       int lock_api_version;
       unsigned supported_locktypes;
       void *(*alloc)(unsigned locktype);
       void (*free)(void *lock, unsigned locktype);
       int (*lock)(unsigned mode, void *lock);
       int (*unlock)(unsigned mode, void *lock);
};

int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);

void evthread_set_id_callback(unsigned long (*id_fn)(void));

struct evthread_condition_callbacks {
        int condition_api_version;
        void *(*alloc_condition)(unsigned condtype);
        void (*free_condition)(void *cond);
        int (*signal_condition)(void *cond, int broadcast);
        int (*wait_condition)(void *cond, void *lock,
            const struct timeval *timeout);
};

int evthread_set_condition_callbacks(
        const struct evthread_condition_callbacks *);

evthread_lock_callbacks 结构描述了您的锁定回调及其功能。对于上述版本,lock_api_version 字段必须设置为 EVTHREAD_LOCK_API_VERSIONsupported_locktypes 字段必须设置为 EVTHREAD_LOCKTYPE_* 常量的位掩码,以描述您可以支持哪些锁类型。(从 2.0.4-alpha 开始,EVTHREAD_LOCK_RECURSIVE 是强制性的,EVTHREAD_LOCK_READWRITE 未使用。)alloc函数必须返回指定类型的新锁。该 free功能必须释放被指定类型的锁持有的所有资源。该lock功能必须设法获取锁在指定的模式,对成功和非零失败返回0。该 unlock函数必须尝试解锁锁,成功时返回 0,失败时返回非零。

公认的锁类型有:

  • 0
    一个常规的、不必要的递归锁。
  • EVTHREAD_LOCKTYPE_RECURSIVE
    不会阻止已经持有它的线程再次需要它的锁。一旦持有它的线程解锁它的次数与它最初被锁定的次数一样多,其他线程就可以获得该锁。
  • EVTHREAD_LOCKTYPE_READWRITE
    一种锁,允许多个线程同时持有它以进行读取,但一次只有一个线程持有它以进行写入。作家排除所有读者。

公认的锁定模式有:

  • EVTHREAD_READ
    仅对于 READWRITE 锁:获取或释放锁以进行读取。
  • EVTHREAD_WRITE
    仅对于 READWRITE 锁:获取或释放用于写入的锁。
  • EVTHREAD_TRY
    仅用于锁定:仅当可以立即获取锁时才获取锁。

id_fn 参数必须是一个函数,返回一个 unsigned long 标识哪个线程正在调用该函数。它必须始终为同一个线程返回相同的数字,并且如果两个不同的线程同时执行,则不能为它们返回相同的数字

evthread_condition_callbacks 结构描述了与条件变量相关的回调。对于上述版本,lock_api_version 字段必须设置为 EVTHREAD_CONDITION_API_VERSIONalloc_condition 函数必须返回一个指向新条件变量的指针。它接收 0 作为其参数。free_condition 函数必须释放条件变量持有的存储和资源wait_condition 函数接受三个参数:alloc_condition 分配的条件、您提供的 evthread_lock_callbacks.alloc 函数分配的锁和可选的超时。每当函数被调用时,锁就会被持有;该函数必须释放锁,并等待直到条件变为信号或直到(可选)超时已经过去。wait_condition 函数应该在出错时返回 -1,0 如果条件已发出信号,则为 1 超时。在它返回之前,它应该确保它再次持有锁。最后,signal_condition 函数应该导致一个线程等待唤醒条件(如果它的广播参数为假)和当前等待唤醒条件的所有线程(如果它的广播参数为真)。只有在持有与条件关联的锁时才会持有它。

有关条件变量的更多信息,请查看 pthreadspthread_cond_* 函数或 Windows 的 CONDITION_VARIABLE 函数的文档。

Examples

For an example of how to use these functions, see evthread_pthread.c and
evthread_win32.c in the Libevent source distribution.

本节中的函数在 <event2/thread.h> 中声明。它们中的大多数首先出现在 Libevent 2.0.4-alpha 中。从 2.0.1-alpha 到 2.0.3-alpha 的 libevent 版本使用较旧的接口来设置锁定功能。event_use_pthreads() 函数要求您将程序链接到 event_pthreads 库。

条件变量函数是 Libevent 2.0.7-rc 中的新功能;添加它们是为了解决一些其他棘手的死锁问题

可以在禁用锁定支持的情况下构建 Libevent。如果是,那么为使用上述线程相关函数而构建的程序将不会运行。

Debugging lock usage

为了帮助调试锁的使用,Libevent 有一个可选的“锁调试”功能,它包装了它的锁调用以捕获典型的锁错误,包括:

  • 解锁我们实际上并未持有的锁

  • 重新锁定非递归锁

如果发生这些锁定错误之一,Libevent 会以断言失败退出。

Interface

void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()

Warning
这个函数必须在创建或使用任何锁之前被调用。为了安全起见,请在设置完线程函数后再调用它
这个函数在Libevent 2.0.4-alpha中是新出现的,名字拼错了 evthread_enable_lock_debuging()。在2.1.2-alpha中,该拼写被修正为evthread_enable_lock_debugging();目前两个名字都支持。

Debugging event usage

在使用 Libevent 可以检测和报告的事件时,有一些常见的错误。它们包括:

  • 将未初始化的 struct 事件视为已初始化。

  • 尝试重新初始化挂起的结构事件。

跟踪哪些事件被初始化需要 Libevent 使用额外的内存和 CPU,因此您应该只在实际调试程序时启用调试模式。

Interface

void event_enable_debug_mode(void);

此函数只能在创建任何 event_base 之前调用。

使用调试模式时,如果您的程序使用大量由 event_assign() [而不是 event_new()] 创建的事件,您可能会耗尽内存。发生这种情况是因为 Libevent 无法判断何时不再使用使用 event_assign() 创建的事件。(当您对其调用 event_free() 时,它可以告诉您 event_new() 事件已变为无效。)如果您想避免在调试时耗尽内存,您可以明确告诉 Libevent 此类事件不再被视为分配

Interface

void event_debug_unassign(struct event *ev);

未启用调试时,调用 event_debug_unassign() 无效。

Example

#include <event2/event.h>
#include <event2/event_struct.h>

#include <stdlib.h>

void cb(evutil_socket_t fd, short what, void *ptr)
{
    /* We pass 'NULL' as the callback pointer for the heap allocated
     * event, and we pass the event itself as the callback pointer
     * for the stack-allocated event. */
    struct event *ev = ptr;

    if (ev)
        event_debug_unassign(ev);
}

/* Here's a simple mainloop that waits until fd1 and fd2 are both
 * ready to read. */
void mainloop(evutil_socket_t fd1, evutil_socket_t fd2, int debug_mode)
{
    struct event_base *base;
    struct event event_on_stack, *event_on_heap;

    if (debug_mode)
       event_enable_debug_mode();

    base = event_base_new();

    event_on_heap = event_new(base, fd1, EV_READ, cb, NULL);
    event_assign(&event_on_stack, base, fd2, EV_READ, cb, &event_on_stack);

    event_add(event_on_heap, NULL);
    event_add(&event_on_stack, NULL);

    event_base_dispatch(base);

    event_free(event_on_heap);
    event_base_free(base);
}

详细的事件调试功能只能在编译时使用 CFLAGS 环境变量“-DUSE_DEBUG”启用。启用此标志后,任何针对 Libevent 编译的程序都将输出一个非常详细的日志,详细说明后端的低级活动。这些日志包括但不限于以下内容:

  • event additions
  • event deletions
  • platform specific event notification information

此功能无法通过 API 调用启用或禁用,因此只能在开发人员构建中使用。

这些调试功能是在 Libevent 2.0.4-alpha 中添加的。

Detecting the version of Libevent

新版本的 Libevent 可以添加功能并删除错误。有时您会想要检测 Libevent 版本,以便您可以:

  • 检测已安装的 Libevent 版本是否足以构建您的程序。

  • 显示用于调试的 Libevent 版本。

  • 检测 Libevent 的版本,以便您可以警告用户有关错误或解决它们。

Interface

#define LIBEVENT_VERSION_NUMBER 0x02000300
#define LIBEVENT_VERSION "2.0.3-alpha"
const char *event_get_version(void);
ev_uint32_t event_get_version_number(void);

宏使 Libevent 库的编译时版本可用;这些函数返回运行时版本。请注意,如果您已将程序动态链接到 Libevent,这些版本可能会有所不同。

您可以获得两种格式的 Libevent 版本:适合向用户显示的字符串,或适合数字比较的 4 字节整数。整数格式使用高字节表示主要版本,第二个字节表示次要版本,第三个字节表示补丁版本,低字节表示发布状态(0 表示发布,非零表示给定发布后的开发系列)。

因此,已发布的 Libevent 2.0.1-alpha 的版本号为 [02 00 01 00]0x020001002.0.1-alpha2.0.2-alpha 之间的开发版本可能具有 [02 00 01 08]0x02000108 的版本号。

Example: Compile-time checks

#include <event2/event.h>

#if !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER < 0x02000100
#error "This version of Libevent is not supported; Get 2.0.1-alpha or later."
#endif

int
make_sandwich(void)
{
        /* Let's suppose that Libevent 6.0.5 introduces a make-me-a
           sandwich function. */
#if LIBEVENT_VERSION_NUMBER >= 0x06000500
        evutil_make_me_a_sandwich();
        return 0;
#else
        return -1;
#endif
}

Example: Run-time checks

#include <event2/event.h>
#include <string.h>

int
check_for_old_version(void)
{
    const char *v = event_get_version();
    /* This is a dumb way to do it, but it is the only thing that works
       before Libevent 2.0. */
    if (!strncmp(v, "0.", 2) ||
        !strncmp(v, "1.1", 3) ||
        !strncmp(v, "1.2", 3) ||
        !strncmp(v, "1.3", 3)) {

        printf("Your version of Libevent is very old.  If you run into bugs,"
               " consider upgrading.\n");
        return -1;
    } else {
        printf("Running with Libevent version %s\n", v);
        return 0;
    }
}

int
check_version_match(void)
{
    ev_uint32_t v_compile, v_run;
    v_compile = LIBEVENT_VERSION_NUMBER;
    v_run = event_get_version_number();
    if ((v_compile & 0xffff0000) != (v_run & 0xffff0000)) {
        printf("Running with a Libevent version (%s) very different from the "
               "one we were built with (%s).\n", event_get_version(),
               LIBEVENT_VERSION);
        return -1;
    }
    return 0;
}

本节中的宏和函数在 <event2/event.h> 中定义。event_get_version() 函数最早出现在 Libevent 1.0c 中;其他的首先出现在 Libevent 2.0.1-alpha 中。

Freeing global Libevent structures

即使您已经释放了用 Libevent 分配的所有对象,也会留下一些全局分配的结构。这通常不是问题:一旦进程退出,无论如何它们都会被清理干净。但是拥有这些结构可能会使一些调试工具混淆,认为 Libevent 正在泄漏资源。如果需要确保 Libevent 已经发布了所有内部库全局数据结构,可以调用:

Interface

void libevent_global_shutdown(void);

此函数不会释放 Libevent 函数返回给您的任何结构。如果您想在退出前释放所有内容,则需要自己释放所有事件、event_basesbufferevents 等。

调用 libevent_global_shutdown() 会使其他 Libevent 函数的行为变得不可预测;除了程序调用的最后一个 Libevent 函数外,不要调用它。一个例外是 libevent_global_shutdown() 是幂等的:即使已经被调用,也可以调用它。

该函数在 <event2/event.h> 中声明。它是在 Libevent 2.1.1-alpha 中引入的。

R2: Getting an event_base

在您可以使用任何有趣的 Libevent 函数之前,您需要分配一个或多个 event_base 结构。每个 event_base 结构都包含一组事件,并且可以轮询以确定哪些事件处于活动状态

如果 event_base 设置为使用锁定,则在多个线程之间访问它是安全的。但是,它的循环只能在单个线程中运行。如果要让多个线程轮询 IO,则需要为每个线程设置一个 event_base

提示
[Libevent 的未来版本可能支持跨多个线程运行事件的 event_bases。]
每个 event_base 都有一个“方法”或一个后端,用于确定哪些事件已准备就绪。公认的方法有:

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32
    用户可以使用环境变量禁用特定后端。如果要关闭kqueue后端,设置EVENT_NOKQUEUE环境变量,依此类推。如果您想从程序中关闭后端,请参阅下面有关 event_config_avoid_method() 的说明。

Setting up a default event_base

event_base_new() 函数使用默认设置分配并返回一个新的事件库。它检查环境变量并返回一个指向新 event_base 的指针。如果有错误,则返回 NULL

在方法中进行选择时,它会选择操作系统支持的最快方法

Interface

struct event_base *event_base_new( void );

对于大多数程序,这就是您所需要的。

event_base_new() 函数在 <event2/event.h> 中声明。它首次出现在 Libevent 1.4.3 中。

Setting up a complicated event_base

如果您想更好地控制获得的 event_base 类型,则需要使用 event_configevent_config 是一个不透明的结构,它保存有关您对 event_base首选项的信息。当你想要一个 event_base 时,你将 event_config 传递给 event_base_new_with_config()

Interface

struct event_config *event_config_new( void );
struct event_base *event_base_new_with_config( const  struct event_config *cfg);
void event_config_free( struct event_config *cfg);

要使用这些函数分配一个 event_base,您可以调用 event_config_new() 来分配一个新的 event_config。然后,您调用 event_config 上的其他函数来告诉它您的需求。最后,您调用 event_base_new_with_config() 以获取新的 event_base。完成后,您可以使用 event_config_free() 释放 event_config

Interface

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

enum event_method_feature {
    EV_FEATURE_ET = 0x01,
    EV_FEATURE_O1 = 0x02,
    EV_FEATURE_FDS = 0x04,
};
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_FLAG_IGNORE_ENV = 0x02,
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,
    enum event_base_config_flag flag);

调用 event_config_avoid_method 告诉 Libevent 按名称避免特定的可用后端。调用 event_config_require_feature() 告诉 Libevent 不要使用任何不能提供所有一组功能的后端。调用 event_config_set_flag() 会告诉 Libevent 在构建事件库时设置下面的一个或多个运行时标志

event_config_require_features 的识别特征值为:

  • EV_FEATURE_ET
    需要一个支持边缘触发 IO 的后端方法

  • EV_FEATURE_O1
    需要一个后端方法,其中添加或删除单个事件,或使单个事件变为活动状态,是一个 O(1) 操作。

  • EV_FEATURE_FDS
    需要一个可以支持任意文件描述符类型的后端方法,而不仅仅是套接字

event_config_set_flag() 的公认选项值为:

  • EVENT_BASE_FLAG_NOLOCK
    不要为 event_base 分配锁。设置此选项可能会为锁定和释放 event_base 节省一点时间,但会使从多个线程访问它变得不安全和不起作用。

  • EVENT_BASE_FLAG_IGNORE_ENV
    在选择要使用的后端方法时,不要检查 EVENT_* 环境变量。在使用这个标志之前要三思:它会让用户更难调试你的程序和 Libevent 之间的交互。

  • EVENT_BASE_FLAG_STARTUP_IOCP
    仅在 Windows 上,此标志使 Libevent 在启动时启用任何必要的 IOCP 调度逻辑,而不是按需启用。

  • EVENT_BASE_FLAG_NO_CACHE_TIME
    不是每次事件循环准备运行超时回调时检查当前时间,而是在每次超时回调后检查它。这可能会使用比您预期更多的 CPU,所以要小心!

  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
    告诉 Libevent,如果它决定使用 epoll 后端,使用更快的基于“changelist”的后端是安全的。epoll-changelist 后端可以避免在同一个 fd 在调用后端的 dispatch 函数之间多次修改其状态的情况不必要的系统调用,但它也会触发内核错误,如果你给 Libevent 克隆的任何 fd 会导致错误的结果dup() 或其变体。如果您使用 epoll 以外的后端,则此标志无效。您还可以通过设置 EVENT_EPOLL_USE_CHANGELIST 环境变量来打开 epoll-changelist 选项。

  • EVENT_BASE_FLAG_PRECISE_TIMER
    默认情况下,Libevent 尝试使用操作系统提供的最快的可用计时机制。如果有一个较慢的计时机制提供更细粒度的计时精度,这个标志会告诉 Libevent 改用该计时机制。如果操作系统不提供这种更慢但更精确的机制,则此标志无效

Note

上述操作 event_config 的函数都在成功时返回 0,在失败时返回 -1。

Interface

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)

此功能目前仅在使用 IOCP 时对 Windows 有用,但将来可能对其他平台有用。调用它会告诉 event_config 它生成的 event_base 应该在多线程处理时尝试充分利用给定数量的 CPU。请注意,这只是一个提示:事件库最终可能会使用比您选择的更多或更少的 CPU

Interface

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

此函数通过在检查更多高优先级事件之前限制可以调用的低优先级事件回调的数量来防止优先级倒置。如果 max_interval 为非空,则事件循环会在每次回调后检查时间,如果 max_interval 已过,则重新扫描高优先级事件。如果 max_callbacks 为非负,则事件循环还会在调用 max_callbacks 回调后检查更多事件。这些规则适用于 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);

这些函数和类型在 <event2/event.h> 中声明。

EVENT_BASE_FLAG_IGNORE_ENV 标志首次出现在 Libevent 2.0.2-alpha 中。EVENT_BASE_FLAG_PRECISE_TIMER 标志首次出现在 Libevent 2.1.2-alpha 中。event_config_set_num_cpus_hint() 函数是 Libevent 2.0.7-rc 中的新函数,而 event_config_set_max_dispatch_interval() 函数是 2.1.1-alpha 中的新函数。本节中的其他所有内容首先出现在 Libevent 2.0.1-alpha 中。

Examining an event_base’s backend method

有时您想查看 event_base 中实际可用的功能,或者它使用的方法。

Interface

const  char **event_get_supported_methods( void );

Example

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]);
}

Note

这个函数返回一个 Libevent 被编译支持的方法列表。当 Libevent 尝试运行时,您的操作系统实际上可能不会全部支持它们。例如,您可能在使用 kqueue 有太多问题而无法使用的 OSX 版本。

Interface

const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);

event_base_get_method() 调用返回 event_base 使用的实际方法的名称event_base_get_features() 调用返回它支持的功能的位掩码

Example

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("");
}

这些函数在 <event2/event.h> 中定义。event_base_get_method() 调用首先在 Libevent 1.4.3 中可用。其他的首先出现在 Libevent 2.0.1-alpha 中。

Deallocating an event_base

完成 event_base 后,可以使用 event_base_free() 解除分配。

Interface

void event_base_free( struct event_base *base);

请注意,此函数不会释放当前与 event_base 关联的任何事件也不会关闭其任何套接字或释放其任何指针

event_base_free() 函数在 <event2/event.h> 中定义。它首先在 Libevent 1.2 中实现。

Setting priorities on an event_base

Libevent 支持在一个事件上设置多个优先级。但是,默认情况下, event_base 仅支持单个优先级。您可以通过调用 event_base_priority_init() 来设置 event_base优先级数量

Interface

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

此函数在成功时返回 0,在失败时返回 -1。所述碱参数是修改event_base,和n_priorities优先级支持的数目。它必须至少为 1。新事件的可用优先级将从 **0(最重要)**到 **n_priorities-1(最不重要)**编号。

有一个常量 EVENT_MAX_PRIORITIES,它设置 n_priorities 值的上限。使用更高的 n_priorities 值调用此函数是错误的。

Note
必须在任何事件变为活动状态之前调用此函数。最好在创建 event_base立即调用它。

要查找基当前支持的优先级数量,您可以调用 event_base_getnpriorities()

Interface

int event_base_get_npriorities( struct event_base *base);

返回值等于base配置的优先级数。因此,如果 event_base_get_npriorities() 返回 3,则允许的优先级值为 0、1 和 2

Example

有关示例,请参阅下面的 event_priority_set 文档。

默认情况下,所有与此基关联的新事件都将以等于 n_priorities / 2 的优先级进行初始化。

event_base_priority_init 函数在 <event2/event.h> 中定义。它从 Libevent 1.0 开始可用。event_base_get_npriorities() 函数是 Libevent 2.1.1-alpha 中的新函数。

Reinitializing an event_base after fork()

并非所有事件后端在调用 fork() 后都保持干净。因此,如果您的程序使用 fork() 或相关系统调用来启动一个新进程,并且您希望在分叉后继续使用 event_base,则可能需要重新初始化它

Interface

int event_reinit( struct event_base *base);

该函数在成功时返回 0,在失败时返回 -1。

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); /*...*/
}

event_reinit() 函数在 <event2/event.h> 中定义。它首先在 Libevent 1.4.3-alpha 中可用。

Obsolete event_base functions

旧版本的 Libevent 非常依赖于“当前”event_base 的想法。“当前” event_base所有线程共享的全局设置。如果你忘记指定你想要的 event_base,你会得到当前的。由于 event_bases 不是线程安全的,因此这很容易出错。

代替 event_base_new(),有:

Interface

struct event_base *event_init( void );

此函数的工作方式类似于 event_base_new(),并将当前基数设置为分配的基数。没有其他方法可以改变当前的基数。

本节中的一些 event_base 函数具有在当前基础上运行的变体。这些函数的行为与当前函数相同,只是它们不接受基本参数

Current functionObsolete current-base version
event_base_priority_init()event_priority_init()
event_base_get_method()event_get_method()

R3: Running an event loop

一旦你有一个注册了一些事件的 event_base(参见下一节关于如何创建和注册事件),你会希望 Libevent 等待事件并提醒你它们。

Running the loop

Interface

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

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

默认情况下, event_base_loop() 函数运行event_base 直到其中没有更多事件注册。为了运行循环,它会反复检查是否有任何已注册的事件已触发(例如,读取事件的文件描述符是否已准备好读取,或者超时事件的超时是否已准备好到期)。一旦发生这种情况,它将所有触发的事件标记为**“活动”**,并开始运行它们。

您可以通过在其flags参数中设置一个或多个标志来更改 event_base_loop() 的行为。如果设置了 EVLOOP_ONCE,则循环将等待直到某些事件变为活动状态,然后运行活动事件,直到没有更多要运行的事件,然后返回。如果设置了 EVLOOP_NONBLOCK,则循环不会等待事件触发:它只会检查是否有任何事件准备好立即触发,如果是,则运行它们的回调。

通常,只要没有挂起或活动的事件,循环就会退出。您可以通过传递 EVLOOP_NO_EXIT_ON_EMPTY 标志来覆盖此行为——例如,如果您要从某个其他线程添加事件。如果您确实设置了 EVLOOP_NO_EXIT_ON_EMPTY,则循环将继续运行,直到有人调用 event_base_loopbreak() 或调用 event_base_loopexit() 或发生错误为止。

完成后,如果 event_base_loop() 正常退出,则返回 0,如果由于后端中的一些未处理的错误而退出,则返回 -1,如果由于没有更多未决或活动事件而退出,则返回 1。

为了帮助理解,这里是 event_base_loop 算法的大致摘要:

Pseudocode

while (any events are registered with the loop,
        or EVLOOP_NO_EXIT_ON_EMPTY was set) {

    if (EVLOOP_NONBLOCK was set, or any events are already active)
        If any registered events have triggered, mark them active.
    else
        Wait until at least one event has triggered, and mark it active.

    for (p = 0; p < n_priorities; ++p) {
       if (any event with priority of p is active) {
          Run all active events with priority of p.
          break; /* Do not run any events of a less important priority */
       }
    }

    if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)
       break;
}

为方便起见,您还可以call:

Interface

int event_base_dispatch(struct event_base *base);

event_base_dispatch() 调用与 event_base_loop() 相同,没有设置标志。因此,它会一直运行直到没有更多注册的事件或直到 event_base_loopbreak()event_base_loopexit() 被调用。

这些函数在 <event2/event.h> 中定义。它们从 Libevent 1.0 开始就存在了。

Stopping the loop

如果您希望活动的事件循环在所有事件都被移除之前停止运行,您可以调用两个稍微不同的函数。

Interface

int event_base_loopexit(struct event_base *base,
                        const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);

event_base_loopexit() 函数告诉 event_base给定时间过去后停止循环。如果tv参数为 NULL,则 event_base 会立即停止循环。如果 event_base 当前正在运行任何活动事件的回调,它将继续运行它们,并且在它们全部运行之前不会退出。

event_base_loopbreak() 函数告诉 event_base 立即退出它的循环。它与 event_base_loopexit(base, NULL) 的不同之处在于,如果 event_base 当前正在运行任何活动事件的回调,它将在完成当前正在处理的事件后立即退出

还要注意 event_base_loopexit(base,NULL)event_base_loopbreak(base) 在没有事件循环运行时的行为不同:loopexit 安排事件循环的下一个实例在下一轮回调运行后立即停止(就像它已经被调用一样与 EVLOOP_ONCE) 而 loopbreak 只停止当前正在运行的循环,如果事件循环没有运行,则无效。

这两种方法都在成功时返回 0,在失败时返回 -1。

Example: Shut down immediately

#include <event2/event.h>

/* Here's a callback function that calls loopbreak */
void cb(int sock, short what, void *arg)
{
    struct event_base *base = arg;
    event_base_loopbreak(base);
}

void main_loop(struct event_base *base, evutil_socket_t watchdog_fd)
{
    struct event *watchdog_event;

    /* Construct a new event to trigger whenever there are any bytes to
       read from a watchdog socket.  When that happens, we'll call the
       cb function, which will make the loop exit immediately without
       running any other active events at all.
     */
    watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);

    event_add(watchdog_event, NULL);

    event_base_dispatch(base);
}

Example: Run an event loop for 10 seconds, then exit.

#include <event2/event.h>

void run_base_with_ticks(struct event_base *base)
{
  struct timeval ten_sec;

  ten_sec.tv_sec = 10;
  ten_sec.tv_usec = 0;

  /* Now we run the event_base for a series of 10-second intervals, printing
     "Tick" after each.  For a much better way to implement a 10-second
     timer, see the section below about persistent timer events. */
  while (1) {
     /* This schedules an exit ten seconds from now. */
     event_base_loopexit(base, &ten_sec);

     event_base_dispatch(base);
     puts("Tick");
  }
}

有时您可能想知道您对 event_base_dispatch()event_base_loop() 的调用是否正常退出,或者因为对 event_base_loopexit()event_base_break() 的调用。您可以使用这些函数来判断是否调用了 loopexitbreak

Interface

int event_base_got_exit( struct event_base *base);
int event_base_got_break( struct event_base *base);

如果循环分别用 event_base_loopexit()event_base_break() 停止,这两个函数将返回 true,否则返回 false。它们的值将在您下次启动事件循环时重置。

这些函数在 <event2/event.h> 中声明。event_break_loopexit() 函数首先在 Libevent 1.0c 中实现;event_break_loopbreak() 首先在 Libevent 1.4.3 中实现。

Re-checking for events

通常,Libevent 检查事件,然后运行具有最高优先级的所有活动事件,然后再次检查事件,依此类推。但有时您可能希望在当前回调运行后立即停止 Libevent,并告诉它再次扫描。与 event_base_loopbreak() 类似,您可以使用函数 event_base_loopcontinue() 执行此操作。

Interface

int event_base_loopcontinue( struct event_base *);

如果我们当前没有运行事件回调,则调用 event_base_loopcontinue() 无效。

这个函数是在 Libevent 2.1.2-alpha 中引入的。

Checking the internal time cache

有时您希望在事件回调中获得当前时间的大致视图,并且希望在不自己调用 gettimeofday() 的情况下获得它(大概是因为您的操作系统将 gettimeofday() 实现为系统调用,而您正试图避免系统调用高架)。

在回调中,您可以向 Libevent 询问它开始执行这一轮回调时的当前时间视图

Interface

int event_base_gettimeofday_cached( struct event_base *base,
     struct timeval *tv_out);

如果 event_base 当前正在执行回调,则 event_base_gettimeofday_cached() 函数将其tv_out参数的值设置为 cache时间。否则,它会调用 evutil_gettimeofday() 获取实际的当前时间。成功时返回 0,失败时返回负数。

请注意,由于在 Libevent 开始运行回调时缓存了 timeval,因此它至少会有点不准确。如果您的回调需要很长时间才能运行,则它可能非常不准确。要强制立即更新缓存,您可以调用此函数:

Interface

int event_base_update_cache_time( struct event_base *base);

成功时返回 0,失败时返回 -1,如果 base 没有运行其事件循环,则无效。

event_base_gettimeofday_cached() 函数是 Libevent 2.0.4-alpha 中的新函数。Libevent 2.1.1-alpha 添加了 event_base_update_cache_time()

Dumping the event_base status

Interface

void event_base_dump_events( struct event_base *base, FILE *f);

为了帮助调试您的程序(或调试 Libevent!),您有时可能需要添加到 event_base 中的所有事件及其状态的完整列表。调用 event_base_dump_events() 将此列表写入提供的 stdio 文件

该列表是人类可读的;它的格式将在未来版本的 Libevent 中改变。

这个函数是在 Libevent 2.0.1-alpha 中引入的。

Running a function over every event in an event_base

Interface

typedef int (*event_base_foreach_event_cb)(const struct event_base *,
    const struct event *, void *);

int event_base_foreach_event(struct event_base *base,
                             event_base_foreach_event_cb fn,
                             void *arg);

您可以使用 event_base_foreach_event() 迭代与 event_base() 关联的每个当前活动或挂起的事件。提供的回调将在每个事件中以未指定的顺序被调用一次。event_base_foreach_event() 的第三个参数将作为第三个参数传递给回调的每次调用。

回调函数必须返回 0 才能继续迭代,或者返回某个其他整数才能停止迭代。回调函数最终返回的任何值都将由 event_base_foreach_function() 返回。

您的回调函数不得修改它接收到的任何事件,或向事件库添加或删除任何事件,或以其他方式修改与事件库关联的任何事件,否则可能发生未定义的行为,直至或包括崩溃和堆粉碎

event_base 锁将在调用 event_base_foreach_event() 期间保持——这将阻止其他线程event_base 执行任何有用的操作,因此请确保您的回调不会花费很长时间

这个函数是在 Libevent 2.1.2-alpha 中添加的。

Obsolete event loop functions

如上所述,旧版本的 Libevent API 具有“当前”event_base 的全局概念。

本节中的一些事件循环函数具有在当前基础上运行的变体。这些函数的行为与当前函数相同,只是它们不接受基本参数。

Current functionObsolete current-base version
event_base_dispatch()event_dispatch()
event_base_loop()event_loop()
event_base_loopexit()event_loopexit()
event_base_loopbreak()event_loopbreak()

Note
因为 event_base 在 Libevent 2.0 之前不支持锁定,所以这些函数不是完全线程安全的:不允许从执行事件循环的线程以外的线程调用 _loopbreak()_loopexit() 函数。

R4: Working with events

libevent 的基本操作单元是event每个事件代表一组条件,包括:

  • 准备读取或写入的文件描述符。

  • 文件描述符成为准备读或写(边沿触发仅IO)。

  • 超时到期。

  • 一个信号出现。

  • 用户触发的事件。

事件具有相似的生命周期。一旦您调用 Libevent 函数来设置事件并将其与事件库相关联,它就会被 初始化。此时,您可以添加,这使其 在基础中挂起。当事件未决时,如果触发事件的条件发生(例如,其文件描述符更改状态或其超时到期),则事件变为活动状态,并且其(用户提供的)回调函数将运行。如果事件配置为 持久性,则它保持挂起状态。如果它不是持久的,它会在回调运行时停止挂起。您可以通过删除一个挂起的事件使其成为非挂起的,并且您可以添加 一个非挂起的事件,使其再次挂起

Constructing event objects

要创建新事件,请使用 event_new() 接口。

Interface

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

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

struct event *event_new(struct event_base *base, evutil_socket_t fd,
    short what, event_callback_fn cb,
    void *arg);

void event_free(struct event *event);

event_new() 函数尝试分配和构造一个新事件以与base一起使用。在什么论据是一组上面列出的标志。(它们的语义在下面描述。)如果fd是非负的,那么我们将观察读取或写入事件的文件。当事件处于活动状态时,Libevent 将调用提供的 cb函数,将其作为参数传递:文件描述符fd,触发的所有事件的位域,以及在构造函数时为arg传入的值。

对于内部错误或无效参数event_new() 将返回 NULL。

所有新事件都已初始化且非挂起。要使事件挂起,请调用 event_add()(记录如下)。

释放事件,请调用 event_free()。对挂起或活动的事件调用 event_free()安全的:这样做会使事件在取消分配之前变为非挂起和非活动状态

Example

#include <event2/event.h>

void cb_func(evutil_socket_t fd, short what, void *arg)
{
        const char *data = arg;
        printf("Got an event on socket %d:%s%s%s%s [%s]",
            (int) fd,
            (what&EV_TIMEOUT) ? " timeout" : "",
            (what&EV_READ)    ? " read" : "",
            (what&EV_WRITE)   ? " write" : "",
            (what&EV_SIGNAL)  ? " signal" : "",
            data);
}

void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
{
        struct event *ev1, *ev2;
        struct timeval five_seconds = {5,0};
        struct event_base *base = event_base_new();

        /* The caller has already set up fd1, fd2 somehow, and make them
           nonblocking. */

        ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func,
           (char*)"Reading event");
        ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func,
           (char*)"Writing event");

        event_add(ev1, &five_seconds);
        event_add(ev2, NULL);
        event_base_dispatch(base);
}

上述函数定义在 <event2/event.h> 中,最早出现在 Libevent 2.0.1-alpha 中。event_callback_fn 类型首先在 Libevent 2.0.4-alpha 中作为 typedef 出现。

The event flags

  • EV_TIMEOUT
    此标志表示在超时过去后变为活动的事件
    构建事件时忽略 EV_TIMEOUT 标志:您可以在添加事件时设置超时,也可以不设置。这是在“什么”参数回调函数时设置超时发生。

  • EV_READ
    此标志表示当提供的文件描述符准备好读取时变为活动的事件

  • EV_WRITE
    此标志表示当提供的文件描述符准备好写入时变为活动的事件

  • EV_SIGNAL
    用于实现信号检测。请参阅下面的“构建信号事件”。

  • EV_PERSIST
    表示事件是持久的。请参阅下面的“关于事件持久性”。

  • EV_ET
    指示事件应该是边缘触发的,如果底层 event_base 后端支持边缘触发的事件。这会影响 EV_READEV_WRITE 的语义。

从 Libevent 2.0.1-alpha 开始,任何数量的事件都可能同时在相同条件下等待处理。例如,如果给定的 fd 准备好读取,您可能有两个事件将变为活动状态。它们的回调运行的顺序是未定义的

这些标志在 <event2/event.h> 中定义。除了在 Libevent 2.0.1-alpha 中引入的 EV_ET 之外,所有这些都在 Libevent 1.0 之前存在。

About Event Persistence

默认情况下,每当挂起事件变为活动状态时(因为它的 fd 准备好读取或写入,或者因为它的超时到期),它就在其回调执行之前变为非挂起。因此,如果您想让事件再次挂起,您可以从回调函数内部再次调用 event_add()

但是,如果在事件上设置了 EV_PERSIST 标志,则该事件是 持久的。 这意味着即使它的回调被激活,事件仍然处于挂起状态。如果您想在其回调中使其非挂起,您可以在其上调用 event_del()

每当事件的回调运行时,持久性事件的超时就会重置。因此,如果您有一个带有 EV_READ|EV_PERSIST 标志和 5 秒超时的事件,该事件将变为活动状态

  • 每当套接字准备好读取时。

  • 自从事件上次激活以来已经过去了五秒钟。

Creating an event as its own callback argument

通常,您可能希望创建一个将自身作为回调参数接收的事件。但是,您不能仅将指向该事件的指针作为参数传递给 event_new(),因为它尚不存在。为了解决这个问题,你可以使用 event_self_cbarg()

Interface

void *event_self_cbarg();

event_self_cbarg() 函数返回一个**“魔术”指针**,当作为事件回调参数传递时,它告诉 event_new() 创建一个接收自身作为其回调参数的事件

Example

#include <event2/event.h>

static int n_calls = 0;

void cb_func(evutil_socket_t fd, short what, void *arg)
{
    struct event *me = arg;

    printf("cb_func called %d times so far.\n", ++n_calls);

    if (n_calls > 100)
       event_del(me);
}

void run(struct event_base *base)
{
    struct timeval one_sec = { 1, 0 };
    struct event *ev;
    /* We're going to set up a repeating timer to get called called 100
       times. */
    ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());
    event_add(ev, &one_sec);
    event_base_dispatch(base);
}

此函数还可与 event_new()evtimer_new()evsignal_new()event_assign()evtimer_assign()evsignal_assign() 一起使用。但是,它不能用作非事件的回调参数

event_self_cbarg() 函数是在 Libevent 2.1.1-alpha 中引入的。

Timeout-only events

为方便起见,您可以使用一组以 evtimer_ 开头的来代替 event_* 调用来分配和操作纯超时事件。除了提高代码的清晰度之外,使用这些宏没有任何好处

Interface

#define evtimer_new(base, callback, arg) \
    event_new((base), -1, 0, (callback), (arg))
#define evtimer_add(ev, tv) \
    event_add((ev),(tv))
#define evtimer_del(ev) \
    event_del(ev)
#define evtimer_pending(ev, tv_out) \
    event_pending((ev), EV_TIMEOUT, (tv_out))

这些宏从 Libevent 0.6 开始就已经存在,除了 evtimer_new(),它首次出现在 Libevent 2.0.1-alpha 中。

Constructing signal events

Libevent 还可以监视 POSIX 风格的信号。要为信号构造处理程序,请使用:

Interface

#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)

参数与 event_new 相同,除了我们提供信号编号而不是文件描述符

Example

struct event *hup_event;
struct event_base *base = event_base_new();

/* call sighup_function on a HUP signal */
hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL);

Note
信号回调在信号发生后在事件循环中运行,因此它们可以安全地调用您不应该从常规 POSIX 信号处理程序调用的函数

Warning
不要在信号事件上设置超时。它可能不受支持。[FIXME:这是真的吗?]
在处理信号事件时,您还可以使用一组方便的

Interface

#define evsignal_add(ev, tv) \
    event_add((ev),(tv))
#define evsignal_del(ev) \
    event_del(ev)
#define evsignal_pending(ev, what, tv_out) \
    event_pending((ev), (what), (tv_out))

evsignal_* 宏从 Libevent 2.0.1-alpha 开始就已经存在。之前的版本将它们称为 signal_add()signal_del() 等。

Caveats when working with signals

使用当前版本的 Libevent,对于大多数后端,每个进程一次只有一个 event_base 可以监听信号。如果您一次将信号事件添加到两个 event_bases —即使信号不同!— 只有一个 event_base接收信号

kqueue 后端没有这个限制

Setting up events without heap-allocation

出于性能和其他原因,有些人喜欢将事件作为更大结构的一部分进行分配。对于事件的每次使用,这会保存它们:

  • 用于在堆上分配小对象的内存分配器开销。

  • 取消引用结构事件指针的时间开销。

  • 如果事件不在缓存中,则可能的额外缓存未命中的时间开销。

使用这种方法可能会破坏与其他版本的 Libevent 的二进制兼容性,这些版本的事件结构可能具有不同的大小。

这些都是非常小的成本,对于大多数应用程序来说无关紧要。您应该坚持使用 event_new() ,除非您知道为堆分配事件会导致显着的性能损失。如果未来版本的 Libevent 使用比您正在构建的事件结构更大的事件结构,则使用 event_assign() 可能会导致难以诊断的错误

Interface

int event_assign(struct event *event, struct event_base *base,
    evutil_socket_t fd, short what,
    void (*callback)(evutil_socket_t, short, void *), void *arg);

event_assign() 的所有参数都与 event_new() 相同,但event参数必须指向未初始化的事件。它在成功时返回 0,在内部错误或错误参数时返回 -1。

Example

#include <event2/event.h>
/* Watch out!  Including event_struct.h means that your code will not
 * be binary-compatible with future versions of Libevent. */
#include <event2/event_struct.h>
#include <stdlib.h>

struct event_pair {
         evutil_socket_t fd;
         struct event read_event;
         struct event write_event;
};
void readcb(evutil_socket_t, short, void *);
void writecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{
        struct event_pair *p = malloc(sizeof(struct event_pair));
        if (!p) return NULL;
        p->fd = fd;
        event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p);
        event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p);
        return p;
}

您还可以使用 event_assign()初始化堆栈分配或静态分配的事件

WARNING

永远不要在事件库中已经挂起的事件上调用 event_assign()。这样做会导致极其难以诊断的错误。如果事件已经初始化并挂起,请在再次调用 event_assign()之前对其调用 event_del()

有一些方便的宏可以用于 event_assign() 仅超时或信号事件

Interface

#define evtimer_assign(event, base, callback, arg) \
    event_assign(event, base, -1, 0, callback, arg)
#define evsignal_assign(event, base, signum, callback, arg) \
    event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)

如果您需要使用 event_assign()并保持与Libevent未来版本的二进制兼容性,您可以要求 Libevent 库在运行时告诉您结构事件应该有多大:

Interface

size_t event_get_struct_event_size( void );

此函数返回您需要为结构事件留出的字节数。和以前一样,只有当您知道堆分配实际上是您程序中的一个重要问题时,才应该使用此函数,因为它会使您的代码更难以阅读和编写

需要注意的是event_get_struct_event_size()可能会在未来给你一个值 小比的sizeof(结构事件)。如果发生这种情况,则意味着struct 事件末尾的任何额外字节只是为未来版本的 Libevent 保留的填充字节。

这是与上面相同的示例,但我们不依赖于event_struct.h 中的struct event的大小,而是使用 event_get_struct_size() 在运行时使用正确的大小。

Example

#include <event2/event.h>
#include <stdlib.h>

/* When we allocate an event_pair in memory, we'll actually allocate
 * more space at the end of the structure.  We define some macros
 * to make accessing those events less error-prone. */
struct event_pair {
         evutil_socket_t fd;
};

/* Macro: yield the struct event 'offset' bytes from the start of 'p' */
#define EVENT_AT_OFFSET(p, offset) \
            ((struct event*) ( ((char*)(p)) + (offset) ))
/* Macro: yield the read event of an event_pair */
#define READEV_PTR(pair) \
            EVENT_AT_OFFSET((pair), sizeof(struct event_pair))
/* Macro: yield the write event of an event_pair */
#define WRITEEV_PTR(pair) \
            EVENT_AT_OFFSET((pair), \
                sizeof(struct event_pair)+event_get_struct_event_size())

/* Macro: yield the actual size to allocate for an event_pair */
#define EVENT_PAIR_SIZE() \
            (sizeof(struct event_pair)+2*event_get_struct_event_size())

void readcb(evutil_socket_t, short, void *);
void writecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{
        struct event_pair *p = malloc(EVENT_PAIR_SIZE());
        if (!p) return NULL;
        p->fd = fd;
        event_assign(READEV_PTR(p), base, fd, EV_READ|EV_PERSIST, readcb, p);
        event_assign(WRITEEV_PTR(p), base, fd, EV_WRITE|EV_PERSIST, writecb, p);
        return p;
}

<event2/event.h> 中定义的 event_assign() 函数。它从 Libevent 2.0.1-alpha 开始就存在了。它从 2.0.3-alpha 开始返回一个 int;以前,它返回无效。event_get_struct_event_size() 函数是在 Libevent 2.0.4-alpha 中引入的。事件结构本身在 <event2/event_struct.h> 中定义。

Making events pending and non-pending

一旦你构建了一个事件,它实际上不会做任何事情,直到你通过添加它使其挂起。您可以使用 event_add 执行此操作:

Interface

int event_add( struct event *ev, const  struct timeval *tv);

在非挂起事件上调用 event_add 使其在其配置的基础中挂起。该函数在成功时返回 0,在失败时返回 -1。如果tvNULL,则添加事件且不会超时。否则,tv是以秒和微秒为单位的超时大小

如果您对已经挂起的事件调用 event_add() ,它将使其挂起,并使用提供的超时重新安排它。如果事件已经挂起,并且您使用超时 NULL 重新添加它,则 event_add() 将无效。

Note
不要将tv设置为您希望超时运行的时间。如果你说“tv→tv_sec = time(NULL)+10;” 2010 年 1 月 1 日,您的超时将等待 40 年,而不是 10 秒。

Interface

int event_del(struct event *ev);

在初始化事件上调用 event_del 使其非挂起和非活动状态。如果事件未挂起或处于活动状态,则没有任何影响。成功时返回值为 0,失败时返回 -1。

Note
如果您在事件变为活动状态后但在其回调有机会执行之前将其删除,则不会执行回调

Interface

int event_remove_timer( struct event *ev);

最后,您可以完全删除挂起事件的超时,而无需删除其 IO 或信号组件。如果事件没有超时挂起,则 event_remove_timer() 无效。如果事件只有超时但没有 IO 或信号组件,则 event_remove_timer()event_del() 具有相同的效果。成功时返回值为 0,失败时返回 -1。

这些在 <event2/event.h> 中定义;event_add()event_del() 从 Libevent 0.1 开始就存在了;event_remove_timer() 是在 2.1.2-alpha 中添加的。

Events with priorities

当多个事件同时触发时,Libevent 没有定义关于何时执行它们的回调的任何顺序。您可以使用优先级将某些事件定义为比其他事件更重要。

如前一节所述,每个 event_base 都有一个或多个与之关联的优先级值。在将事件添加到 event_base 之前,但在对其进行初始化之后,您可以设置其优先级

Interface

int event_priority_set( struct event *event, int priority);

事件的优先级是一个介于 0 和 event_base 中的优先级数减 1 之间的数字。该函数在成功时返回 0,在失败时返回 -1。

多个优先级的多个事件变为活动状态时,低优先级的事件不会运行。相反,Libevent 运行高优先级事件,然后再次检查事件。只有当没有高优先级事件处于活动状态时,低优先级事件才会运行

Example

#include <event2/event.h>

void read_cb(evutil_socket_t, short, void *);
void write_cb(evutil_socket_t, short, void *);

void main_loop(evutil_socket_t fd)
{
  struct event *important, *unimportant;
  struct event_base *base;

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

  /* Now, whenever the fd is ready for writing, the write callback will
     happen before the read callback.  The read callback won't happen at
     all until the write callback is no longer active. */
}

当您没有为事件设置优先级时,默认值为事件库中的队列数除以 2。

该函数在 <event2/event.h> 中声明。它从 Libevent 1.0 开始就存在了。

Inspecting event status

有时您想判断是否添加了一个事件,并检查它指的是什么。

Interface

int event_pending(const struct event *ev, short what, struct timeval *tv_out);

#define event_get_signal(ev) /* ... */
evutil_socket_t event_get_fd(const struct event *ev);
struct event_base *event_get_base(const struct event *ev);
short event_get_events(const struct event *ev);
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);
int event_get_priority(const struct event *ev);

void event_get_assignment(const struct event *event,
        struct event_base **base_out,
        evutil_socket_t *fd_out,
        short *events_out,
        event_callback_fn *callback_out,
        void **arg_out);

event_pending 函数确定给定的事件是待处理的还是活动的。如果是,并且在what参数中设置了任何标志 EV_READEV_WRITEEV_SIGNALEV_TIMEOUT ,则该函数返回事件当前挂起或活动的所有标志。如果提供了tv_out,并且在what 中设置了 EV_TIMEOUT ,并且事件当前处于挂起状态或在超时时处于活动状态,则tv_out设置为保持事件超时到期的时间

event_get_fd()event_get_signal() 函数返回为事件配置的文件描述符或信号编号event_get_base() 函数返回其配置的 event_baseevent_get_events() 函数返回事件的事件标志(EV_READEV_WRITE 等)。event_get_callback()event_get_callback_arg() 函数返回回调函数和参数指针event_get_priority() 函数返回事件当前分配的优先级

event_get_assignment() 函数将事件的所有分配字段复制到提供的指针中。如果任何指针为 NULL,则将其忽略。

Example

#include <event2/event.h>
#include <stdio.h>

/* Change the callback and callback_arg of 'ev', which must not be
 * pending. */
int replace_callback(struct event *ev, event_callback_fn new_callback,
    void *new_callback_arg)
{
    struct event_base *base;
    evutil_socket_t fd;
    short events;

    int pending;

    pending = event_pending(ev, EV_READ|EV_WRITE|EV_SIGNAL|EV_TIMEOUT,
                            NULL);
    if (pending) {
        /* We want to catch this here so that we do not re-assign a
         * pending event.  That would be very very bad. */
        fprintf(stderr,
                "Error! replace_callback called on a pending event!\n");
        return -1;
    }

    event_get_assignment(ev, &base, &fd, &events,
                         NULL /* ignore old callback */ ,
                         NULL /* ignore old callback argument */);

    event_assign(ev, base, fd, events, new_callback, new_callback_arg);
    return 0;
}

这些函数在 <event2/event.h> 中声明。event_pending() 函数从 Libevent 0.1 开始就存在了。Libevent 2.0.1-alpha 引入了 event_get_fd()event_get_signal()。Libevent 2.0.2-alpha 引入了 event_get_base()。Libevent 2.1.2-alpha 添加了 event_get_priority()。其他是 Libevent 2.0.4-alpha 中的新内容。

Finding the currently running event

出于调试或其他目的,您可以获得指向当前正在运行的事件的指针

Interface

struct event *event_base_get_running_event( struct event_base *base);

Note
此函数的行为仅在从提供的 event_base 循环中调用时才定义不支持从另一个线程调用它,并且可能导致未定义的行为

该函数在 <event2/event.h> 中声明。它是在 Libevent 2.1.1-alpha 中引入的。

Configuring one-off events

如果您不需要多次添加一个事件,或者一旦添加就删除它,并且它不必是持久的,您可以使用 event_base_once()

Interface

int event_base_once(struct event_base *, evutil_socket_t, short,
  void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);

该函数的接口与 event_new() 相同,只是它不支持 EV_SIGNALEV_PERSIST。计划事件以默认优先级插入和运行。当回调最终完成时,Libevent 释放内部事件结构本身。成功时返回值为 0,失败时返回 -1。

使用 event_base_once 插入的事件无法删除或手动激活:如果您希望能够取消事件,请使用常规的 event_new()event_assign() 接口创建它。
Note
在 Libevent 2.0 之前,如果事件从未被触发,则用于保存它的内部存储器将永远不会被释放。从 Libevent 2.1.2-alpha 开始,这些事件在 event_base被释放时被释放,即使它们没有被激活,但仍然要注意:如果有一些与它们的回调参数相关联的存储,除非你的程序已经做了一些事情来跟踪和发布它。

Manually activating an event

极少情况下,即使事件的条件尚未触发,您也可能希望使其处于活动状态。

Interface

void event_active( struct event *ev, int what, short ncalls);

此函数使事件ev变为活动状态,并带有标记what (EV_READEV_WRITEEV_TIMEOUT 的组合)。该事件不需要之前一直处于挂起状态,并且激活它不会使其处于挂起状态。

Warning:

对同一事件递归调​​用 event_active() 可能会导致资源耗尽。以下代码片段是如何错误使用 event_active 的示例。

Bad Example: making an infinite loop with event_active()

struct event *ev;

static void cb(int sock, short which, void *arg) {
        /* Whoops: Calling event_active on the same event unconditionally
           from within its callback means that no other events might not get
           run! */

        event_active(ev, EV_WRITE, 0);
}

int main(int argc, char **argv) {
        struct event_base *base = event_base_new();

        ev = event_new(base, -1, EV_PERSIST | EV_READ, cb, NULL);

        event_add(ev, NULL);

        event_active(ev, EV_WRITE, 0);

        event_base_loop(base, 0);

        return 0;
}

这会造成事件循环只执行一次并永远调用函数“cb”的情况

Example: Alternative solution to the above problem using timers

struct event *ev;
struct timeval tv;

static void cb(int sock, short which, void *arg) {
   if (!evtimer_pending(ev, NULL)) {
       event_del(ev);
       evtimer_add(ev, &tv);
   }
}

int main(int argc, char **argv) {
   struct event_base *base = event_base_new();

   tv.tv_sec = 0;
   tv.tv_usec = 0;

   ev = evtimer_new(base, cb, NULL);

   evtimer_add(ev, &tv);

   event_base_loop(base, 0);

   return 0;
}

Example: Alternative solution to the above problem using event_config_set_max_dispatch_interval()

struct event *ev;

static void cb(int sock, short which, void *arg) {
        event_active(ev, EV_WRITE, 0);
}

int main(int argc, char **argv) {
        struct event_config *cfg = event_config_new();
        /* Run at most 16 callbacks before checking for other events. */
        event_config_set_max_dispatch_interval(cfg, NULL, 16, 0);
        struct event_base *base = event_base_new_with_config(cfg);
        ev = event_new(base, -1, EV_PERSIST | EV_READ, cb, NULL);

        event_add(ev, NULL);

        event_active(ev, EV_WRITE, 0);

        event_base_loop(base, 0);

        return 0;
}

该函数在 <event2/event.h> 中定义。它从 Libevent 0.3 开始就存在了。

Optimizing common timeouts

当前版本的 Libevent 使用二进制堆算法来跟踪挂起事件的超时二叉堆为添加和删除每个事件超时提供了 O(lg n) 阶的性能。如果您添加具有随机分布的超时值集的事件,这是最佳选择,但如果您有大量具有相同超时值的事件,则不是这样。

例如,假设您有一万个事件,每个事件都应在添加五秒后触发其超时。在这种情况下,您可以通过使用双向链接队列实现为每次超时获得 O(1) 性能。

自然,您不希望为所有超时值使用队列,因为队列仅对于恒定超时值更快。如果某些超时或多或少是随机分布的,那么将这些超时之一添加到队列将花费 O(n) 时间,这将比二进制堆要糟糕得多。

Libevent 允许您通过将一些超时放在队列中将其他超时放在二进制堆中来解决这个问题。为此,您需要向 Libevent 请求一个特殊的**“公共超时”时间值**,然后您可以使用它来添加具有该时间值的事件。如果您有大量具有单个公共超时的事件,则使用此优化应该可以提高超时性能。

Interface

const struct timeval *event_base_init_common_timeout(
    struct event_base *base, const struct timeval *duration);

此函数将 event_base公共超时的持续时间作为其参数进行初始化。它返回一个指向特殊结构 timeval 的指针,您可以使用该指针指示应将事件添加到 O(1) 队列而不是 O(lg n) 堆。这个特殊的时间值可以在你的代码中自由复制或分配。它仅适用于您用来构建它的特定基础。不要依赖它的实际内容:Libevent 使用它们来告诉自己使用哪个队列。

Example

#include <event2/event.h>
#include <string.h>

/* We're going to create a very large number of events on a given base,
 * nearly all of which have a ten-second timeout.  If initialize_timeout
 * is called, we'll tell Libevent to add the ten-second ones to an O(1)
 * queue. */
struct timeval ten_seconds = { 10, 0 };

void initialize_timeout(struct event_base *base)
{
    struct timeval tv_in = { 10, 0 };
    const struct timeval *tv_out;
    tv_out = event_base_init_common_timeout(base, &tv_in);
    memcpy(&ten_seconds, tv_out, sizeof(struct timeval));
}

int my_event_add(struct event *ev, const struct timeval *tv)
{
    /* Note that ev must have the same event_base that we passed to
       initialize_timeout */
    if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)
        return event_add(ev, &ten_seconds);
    else
        return event_add(ev, tv);
}

与所有优化函数一样,您应该避免使用 common_timeout 功能,除非您非常确定它对您很重要。

此功能是在 Libevent 2.0.4-alpha 中引入的。

Telling a good event apart from cleared memory

Libevent 提供了一些函数,您可以使用这些函数将初始化事件与已通过将其设置为 0 清除的内存区分开来(例如,通过使用 calloc() 分配它或使用 memset()bzero() 清除它)。

Interface

int event_initialized( const  struct event *ev); 

#define evsignal_initialized(ev) event_initialized(ev) 
#define evtimer_initialized(ev) event_initialized(ev)

Warning

这些函数无法可靠地区分已初始化的事件一大块未初始化的内存。除非您知道有问题的内存已清除或初始化为事件,否则不应使用它们。

通常,除非您有一个非常具体的应用程序,否则您不需要使用这些函数。event_new() 返回的事件总是被初始化。

Example

#include <event2/event.h>
#include <stdlib.h>

struct reader {
    evutil_socket_t fd;
};

#define READER_ACTUAL_SIZE() \
    (sizeof(struct reader) + \
     event_get_struct_event_size())

#define READER_EVENT_PTR(r) \
    ((struct event *) (((char*)(r))+sizeof(struct reader)))

struct reader *allocate_reader(evutil_socket_t fd)
{
    struct reader *r = calloc(1, READER_ACTUAL_SIZE());
    if (r)
        r->fd = fd;
    return r;
}

void readcb(evutil_socket_t, short, void *);
int add_reader(struct reader *r, struct event_base *b)
{
    struct event *ev = READER_EVENT_PTR(r);
    if (!event_initialized(ev))
        event_assign(ev, b, r->fd, EV_READ, readcb, r);
    return event_add(ev, NULL);
}

event_initialized() 函数从 Libevent 0.3 开始就存在了。

Obsolete event manipulation functions

Libevent 2.0 之前的版本没有 event_assign()event_new()。相反,您有 event_set(),它将事件与“当前”基础相关联。如果您有多个碱基,则需要记住之后调用 event_base_set() 以确保该事件与您实际想要使用的碱基相关联。

Interface

void event_set(struct event *event, evutil_socket_t fd, short what,
        void(*callback)(evutil_socket_t, short, void *), void *arg);
int event_base_set(struct event_base *base, struct event *event);

event_set() 函数类似于 event_assign(),除了它使用当前基数。event_base_set() 函数更改与事件关联的基数

为了更方便地处理定时器和信号,有一些 event_set() 变体:evtimer_set() 大致对应于 evtimer_assign(),而 evsignal_set() 大致对应于 evsignal_assign()

2.0 之前的 Libevent 版本使用“signal_”作为 event_set() 等基于信号的变体的前缀,而不是“evsignal_”。(也就是说,他们有 signal_set()signal_add()signal_del()signal_pending()signal_initialized()。)真正古老的 Libevent 版本(0.6 之前)使用“timeout_”而不是“evtimer_”。因此,如果您在进行代码考古,您可能会看到 timeout_add()timeout_del()timeout_initialized()timeout_set()timeout_pending() 等。

代替 event_get_fd()event_get_signal() 函数,旧版本的 Libevent(2.0 之前)使用了两个名为 EVENT_FD()EVENT_SIGNAL() 的宏。这些宏直接检查事件结构的内容,从而防止版本之间的二进制兼容性;在 2.0 及更高版本中,它们只是 event_get_fd()event_get_signal() 的别名。

由于 2.0 之前的 Libevent 版本没有锁定支持,因此从运行 base线程外部调用任何更改 event 相对于 base 的事件状态的函数都是不安全的。这些包括 event_add()event_del()event_active()event_base_once()

还有一个 event_once() 函数起到了 event_base_once() 的作用,但是使用了当前的基数

在 Libevent 2.0 之前,EV_PERSIST 标志没有与超时进行合理的互操作EV_PERSIST 标志在事件被激活时重置超时而不是对超时执行任何操作

2.0 之前的 libevent 版本不支持使用相同的 fd相同的 READ/WRITE 同时插入多个事件。换句话说,一次只能等待一个事件在每个 fd等待读取,并且一次只能等待一个事件在每个 fd等待写入

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值