设置libevent
设置libevent
libevent有一些全局的设置会影响整个libevent库。你必须在调用库的任何函数之前进行设置,否则会导致libevent处于不一致的状态。
记录日志
libevent可以记录内部的错误和警告,如果编译时支持日志记录,它还会记录调试信息。默认情况下,这些信息被写入stderr,你可以提供自己的日志函数来覆盖这些行为。
#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);
你可以写一个符合event_log_cb
签名的函数,然后传递给event_set_log_callback
来重写libevent默认的日志行为。当libevent需要写日志的时候,它会传递给你提供的函数。你可以调用event_set_log_callback
,并且传递NULL参数来恢复libevent默认的日志行为。
#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);
}
注意:
在自己的日志函数里面,再次调用libevent的函数并不安全!举个例子,如果你想在日志回调里面用bufferevents把消息通过网络发送出去,你这样会进入奇怪的、难以调试的bug里面。这些限制可能会在未来版本的libevent中移除。
一般的,调试日志没有开启,所以不会发送至日志回调,如果libevent在编译的时候支持的话,你可以手动开启。
#define EVENT_DBG_NONE 0
#define EVENT_DBG_ALL 0xffffffffu
void event_enable_debug_logging(ev_uint32_t which);
调试日志是冗长的,而且在多数场景下并没有用。给event_enable_debug_logging
传递EVENT_DBG_NONE
参数为默认的日志记录行为,传递EVENT_DBG_ALL
会开启所有调试日志,未来可能会提供更细粒度的控制。
这些函数声明在<event2/event.h>
头文件中,他们首次出现在1.0c
版本中,event_enable_debug_logging
首次出现在2.1.1-alpha
中。
处理致命错误
当Libevent检测到不可恢复的内部错误(例如损坏的数据结构)时,其默认行为是调用exit()
或abort()
以退出当前正在运行的进程。这些错误几乎总是意味着某处存在bug:要么在您的代码中,要么在Libevent本身中。
如果您希望应用程序更优雅地处理致命错误,您可以提供一个Libevent应该调用的函数来代替退出,覆盖 Libevent的默认行为。
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>
头文件中,首次出现在2.0.3-alpha
中。
内存管理
默认情况下,Libevent使用C库的内存管理函数从堆中分配内存。您可以通过为malloc、realloc和free提供您自己的替代品,让Libevent使用另一个内存管理器。如果您希望Libevent使用更高效的分配器,或者您希望Libevent使用检测的分配器来查找内存泄漏,则您可能想要这样做。
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在多线程中使用。
#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);
}
注意
- 替代内存管理函数会影响libevent以后的所有内存相关函数调用,
allocate
、resize
和free
,所以你必须确保在调用任何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>
中,首次出现在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()
的存在。
锁与线程
正如你所知道的那样,当你写多线程的程序的时候,多个线程同时访问一块数据并不总是安全的。
libevent结构通常可以通过多线程以三种方式工作。
- 某些结构是单线程的:从多线程并发访问总是不安全的。
- 某些结构是可选加锁的:可以为每个对象告诉libevent是否需要同时从多个线程使用它。
- 某些结构总是加锁的:如果libevent以支持锁的方式运行,在多线程访问总是安全的。
想在libevent中使用锁,你必须要在libevent分配任何在多线程间共同使用的结构体之前,告诉libevent要使用哪个锁函数。
如果您正在使用pthreads库或本机Windows线程代码,那么您很幸运。有一些预定义的函数可以将Libevent设置为使用正确的pthread或Windows函数。
#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
然后你需要使用evthread_set_lock_callbacks
和evthread_set_id_callback
告诉libevent这些函数。
#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
仅对读写锁有效,为了读而获取或释放锁。
EVTHREAD_WRITE
仅对读写锁有效,为了写而获取或释放锁。
EVTHREAD_TRY
尝试加锁,仅当立刻可以获取锁的时候才加锁。
id_fn
参数必须是一个函数,返回一个unsigned long
标识哪个线程正在调用该函数。它必须始终为同一个线程返回相同的数字,并且如果两个不同的线程同时执行,则不能为它们返回相同的数字。
调试event用法
在使用libevent
可以检测和报告的事件时,有一些常见的错误。它们包括:
- 将未初始化的event结构视为已初始化。
- 尝试重新初始化挂起的event结构。
跟踪libevent初始化的事件结构需要额外的内存和cpu,所以不到万不得已,不要开始调试模式。
void event_enable_debug_mode(void);
此函数需要在event_base
被创建之前调用。
使用调试模式时,如果您的程序使用大量由event_assign()
[而不是event_new()
] 创建的事件,您可能会耗尽内存。发生这种情况是因为Libevent
无法判断何时不再使用使用event_assign()
创建的事件。(当您对其调用 event_free()
时,它可以告诉您event_new()
事件已变为无效。)如果您想避免在调试时耗尽内存,您可以明确告诉 Libevent 此类事件不再被视为分配:
void event_debug_unassign(struct event *ev);
当调试模式没有启用的时候调用此函数没有任何影响。
#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
- 删除event
- 平台特定事件通知信息
此功能无法通过 API 调用启用或禁用,因此只能在开发人员构建中使用。
检测libevent的版本
新版本的libevent可能会添加新特性或者修复bug,所以你可能需要检测libevent的版本:
- 检测已安装的 Libevent 版本是否足以构建您的程序。
- 显示用于调试的 Libevent 版本。
- 检测 Libevent 的版本,以便您可以警告用户有关错误或解决它们。
#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 表示发布,非0表示给定发布后的开发系列)。
因此,已发布的 Libevent 2.0.1-alpha 的版本号为 [02 00 01 00] 或 0x02000100。 2.0.1-alpha 和 2.0.2-alpha 之间的开发版本可能具有 [02 00 01 08] 或 0x02000108 的版本号。
#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
中。
释放全局的libevent结构体
即使您已经释放了使用 Libevent 分配的所有对象,也会留下一些全局分配的结构。这通常不是问题:一旦进程退出,无论如何它们都会被清理。但是拥有这些结构可能会使一些调试工具混淆,认为 Libevent 正在泄漏资源。如果需要确保 Libevent 已经发布了所有内部库全局数据结构,可以调用:
void libevent_global_shutdown(void);
此函数不会释放 Libevent 函数返回给您的任何结构。如果您想在退出前释放所有内容,则需要自己释放所有event
、event_bases
、bufferevents
等。
调用libevent_global_shutdown
函数会导致其它libevent函数出现未定义的行为,所以确保在最后再调用此函数。
该函数在<event2/event.h>
中声明。它是在Libevent 2.1.1-alpha
中引入的。