A Libevent Reference Manual
- 参考链接
- R0: Preliminaries
- R1: Setting up the Libevent library
- R2: Getting an event_base
- R3: Running an event loop
- R4: Working with events
- Constructing event objects
- The event flags
- About Event Persistence
- Creating an event as its own callback argument
- Timeout-only events
- Constructing signal events
- Setting up events without heap-allocation
- Making events pending and non-pending
- Events with priorities
- Inspecting event status
- Finding the currently running event
- Configuring one-off events
- Manually activating an event
- Optimizing common timeouts
- Telling a good event apart from cleared memory
- Obsolete event manipulation functions
参考链接
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分为以下几个组件:
-
实用程序
抽象出不同平台网络实现之间差异的通用功能。 -
event
和event_base
这是 Libevent 的核心。它为各种特定于平台的、基于事件的非阻塞 IO 后端提供抽象 API。它可以让您知道套接字何时准备好读取或写入、执行基本超时功能以及检测操作系统信号。 -
缓冲事件
这些函数为 Libevent 基于事件的核心提供了一个更方便的包装器。它们让您的应用程序请求缓冲读取和写入,而不是在套接字准备就绪时通知您,而是让您知道 IO 何时实际发生。
bufferevent
接口还有多个后端,因此它可以利用提供更快方法来执行非阻塞 IO 的系统,例如Windows IOCP API
。 -
缓冲区
该模块实现了缓冲事件底层的缓冲区,并提供了高效和/或方便访问的功能。 -
evhttp
一个简单的 HTTP 客户端/服务器实现。 -
evdns
一个简单的 DNS 客户端/服务器实现。 -
虚拟主机
一个简单的 RPC 实现。
The Libraries
构建 Libevent 时,默认情况下它会安装以下库:
-
libevent_core
所有核心事件和缓冲区功能。该库包含所有event_base
、evbuffer
、bufferevent
和实用程序函数。 -
libevent_extra
该库定义了您的应用程序可能需要也可能不需要的特定于协议的功能,包括HTTP
、DNS
和RPC
。 -
自由事件
这个library的存在是出于历史原因;它包含libevent_core
和libevent_extra
的内容。你不应该使用它;它可能会在未来版本的 Libevent 中消失。
以下库仅安装在某些平台上:
-
libevent_pthreads
该库基于pthreads
可移植线程库添加了线程和锁定实现。它与libevent_core
分开,因此您不需要链接pthreads
来使用 Libevent,除非您实际上以多线程方式使用 Libevent。 -
libevent_openssl
该库提供对使用bufferevents
和OpenSSL
库的加密通信的支持。它与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.h | event2/event*.h, event2/buffer*.h event2/bufferevent*.h event2/tag*.h |
evdns.h | event2/dns*.h |
evhttp.h | event2/http*.h |
evrpc.h | event2/rpc*.h |
evutil.h | event2/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 库的内存管理函数从堆分配内存。您可以通过为 malloc
、realloc
和 free
提供您自己的替代品,让 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_callbacks
和 evthread_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_VERSION
。supported_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_VERSION
。alloc_condition
函数必须返回一个指向新条件变量的指针。它接收 0 作为其参数。free_condition
函数必须释放条件变量持有的存储和资源。wait_condition
函数接受三个参数:alloc_condition
分配的条件、您提供的 evthread_lock_callbacks
.alloc 函数分配的锁和可选的超时。每当函数被调用时,锁就会被持有;该函数必须释放锁,并等待直到条件变为信号或直到(可选)超时已经过去。wait_condition
函数应该在出错时返回 -1,0 如果条件已发出信号,则为 1 超时。在它返回之前,它应该确保它再次持有锁。最后,signal_condition
函数应该导致一个线程等待唤醒条件(如果它的广播参数为假)和当前等待唤醒条件的所有线程(如果它的广播参数为真)。只有在持有与条件关联的锁时才会持有它。
有关条件变量的更多信息,请查看 pthreads
的 pthread_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]
或 0x02000100
。2.0.1-alpha
和 2.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_bases
、bufferevents
等。
调用 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_config
。event_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 function | Obsolete 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()
的调用。您可以使用这些函数来判断是否调用了 loopexit
或 break
:
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 function | Obsolete 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_READ
和EV_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。如果tv
为 NULL
,则添加事件且不会超时。否则,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_READ
、EV_WRITE
、EV_SIGNAL
和 EV_TIMEOUT
,则该函数返回事件当前挂起或活动的所有标志。如果提供了tv_out
,并且在what 中设置了 EV_TIMEOUT
,并且事件当前处于挂起状态或在超时时处于活动状态,则tv_out
设置为保持事件超时到期的时间。
event_get_fd()
和 event_get_signal()
函数返回为事件配置的文件描述符或信号编号。event_get_base()
函数返回其配置的 event_base
。event_get_events()
函数返回事件的事件标志(EV_READ
、EV_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_SIGNAL
或 EV_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_READ
、EV_WRITE
和 EV_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
上等待写入。