Libevent有一些全局参数,设置的内容会影响整个库,进而影响到整个进程。若需要修改这些参数,你需要在使用Libevent之前进行设置,否则可能会因参数前后不一致而导致挂起。
打印日志
Libevent能记录错误和警告消息。如果在编译时加入debug开关,那也能记录它自身运行的debug日志。这些日志默认写到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打印日志时,日志内容就传到你定义的日志打印函数。
若要恢复默认设置, 再调用一次event_set_log_callback()
, 传一个NULL参数就可以了。
Examples
#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给sock发送一个消息,这将导致一些难以定位的bug。
在未来的版本中一些Libevent函数可能就没有这个限制。
Libevent自身的debug日志功能没有打开。如果Libevent支持自身debug日志,那么你可以手动打开。
#define EVENT_DBG_NONE 0
#define EVENT_DBG_ALL 0xffffffffu
void event_enable_debug_logging(ev_uint32_t which);
Libevent的debug日志很多,而且多数情况下没什么用处, 调用event_enable_debug_logging()
, 使用参数EVENT_DBG_NONE
来设置默认行为, 使用参数EVENT_DBG_ALL
则打开所有支持的debug日志。 未来版本可能支持更多参数。
这些函数定义在头文件<event2/event.h>
。 除了event_enable_debug_logging()
是在2.1.1-alpha版本中才出现,其他函数在版本1.0c中首次出现。
兼容备忘
在Libevent 2.0.19-stable这个版本之前,以EVENT_LOG_
为前缀的这一类宏是这样命名的:
_EVENT_LOG_DEBUG
_EVENT_LOG_MSG
_EVENT_LOG_WARN
_EVENT_LOG_ERR
除非兼容老版本Libevent 2.0.18-stable或更早的旧版本,不应再使用这一类宏了。 未来版本中将移除这些宏。
Handling fatal errors 出错处理
当Libevent监测到不可恢复的内部错误时,比如遇到一个毁坏的数据结构,默认是调用exit()
或者abort()
退出当前进程。 这些错误一般说明某处代码有bug,要么是在你的代码里,要么在Libevent自身代码里。
Libevent提供一个函数让你可以设置你的自定义函数来更为优雅地处理这类错误。
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
使用上,你先定义一个新的函数,再调用event_set_fatal_callback()
设置你的错误处理函数。后续遇到这类致命的错误,Libevent就会调用你的函数。
你的错误处理函数里不应再返回到Libevent里,否则将会导致无法预计的错误,Libevent也可能在后续退出或崩溃。 一旦开始执行你的错误处理函数,也不能再调用其他Libevent函数。
这个函数首次出现在Libevent 2.0.3-alpha,定义在<event2/event.h>
。
内存管理
Libevent默认使用C库的内存管理函数从堆上分配内存。如果你有更高效率的内存管理方法,或者为了检查内存泄漏,你可以设置使用你的函数来替代malloc, realloc, free。
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,那实际上你可能想在这里加一个锁来预防错误。
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);
}
注意
- 替换内存管理函数影响到Libevent后续调用allocate,resize或free,你需要在调用其他Libevent函数前设置并替换你的内存管理函数。 否则Libevent将会使用你的free函数去释放那些使用C库申请内存获得的内存
- 你的malloc和realloc函数需要返回跟C库一样对齐长度(alignment)的内存块
- 你的realloc函数要能正确处理
realloc(NULL, sz)
, 相当于malloc(sz)
- 你的relloc函数要能正确处理
realloc(ptr, 0)
, 相当于free(ptr)
- 你的free函数不需要处理
free(NUUL)
- 你的malloc函数不需要处理
malloc(0)
- 你的内存管理函数需要线程安全
- Libevnet使用从你的内存管理函数申请到的内存,假若Libevent返回这个内存回来,而且你也替换了你的free函数,那你需要负责调用你的free函数来释放这块内存
这个event_set_mem_functions()
函数定义在头文件<event2/event.h>
中,首次出现在Libevent 2.0.1-alpha。
Libevent可能关闭了event_set_mem_functions()
,那么使用这个函数的那一行代码将不会进行编译和链接。 在Libevent 2.0.2-alpha之后, 你可以检查宏EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED
是否定义来检查Libevent是否支持这个函数。
Locks and threading 锁和线程
如你所知,多线程中同时获取数据并不总是安全的。 Libevent能以三种方式在多线程中正常运行。
- 一些数据结构只在单个线程中使用:在多个线程中同时使用同一份数据从来不是安全的
- 一些数据结构选择性加锁:你可以告诉Libevent某个数据是否同时用于多个线程
- 一些数据结构一直加锁:如果Libevent能够加锁,那么在同个线程中使用这些数据是安全的
你需要告诉Libevent使用哪个函数来进行加锁,且需要在申请任何共享数据之前做这个设置。
如果你使用pthread库,或者使用Windowns threading代码,那么有预定义的宏来告诉Libevent来使用pthreads或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
2个函数返回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,非0表示失败
- unlock这个函数必须尝试释放锁,成功返回0,非0表示失败
可以使用的锁的类型有:
0
: 普通的、非必要的递归锁EVTHREAD_LOCKTYPE_RECURSIVE
: 递归锁,同一个线程可以再次获得同个锁,该线程加锁多少次,则解锁同等次数之后,其他线程才能获得这个锁EVTHREAD_LOCKTYPE_READWRITE
: 读写锁,读锁可以被多个线程同时获取,但同一时间内只有一个线程能获取写锁
可以使用的加锁模式有:
EVTHREAD_READ
: 只限READWRITE
, 获取或释放该锁进行读操作EVTHREAD_WRITE
: 只限READWRITE
, 获取或释放该锁进行写操作EVTHREAD_TRY
: 尝试获取锁,非阻塞,在尝试的时候如果能立刻获得锁则获取该锁
函数evthread_set_id_callback
的参数是一个函数指针id_fn,必须返回一个unsigned long值来标示是哪个线程在调用这个函数,且同个线程必须返回相同的值,也不能为同一时间运行着2个线程返回同样的值。
数据结构evthread_condition_callbacks
表示条件锁。各个参数如下:
- lock_api_version相对于上面,必须设置为
EVTHREAD_CONDITION_API_VERSION
- alloc_condition必须返回一个新创建的条件锁,入参是
0
- free_condition释放一个条件锁持有的资源
- wait_condition需要3个入参:
a) alloc_condition创建的条件锁
b) evthread_lock_callbacks.alloc这个函数创建的锁
c) 一个可选的超时时间
调用这个函数后就能持有条件锁,也必须释放该锁。该函数一直等待,直到条件满足发起信号,或者直到超时;返回-1表示出错,0表示条件发生了,1表示超时。返回前它要再确保它持有该锁。最后,这个函数要唤醒等待条件发送的线程,如果设置了广播,则要唤醒当前所有等待的线程。
更多关于条件锁,参考pthread的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
这个库文件。
条件变量的这些函数是在2.0.7-rc才开始使用, 为了解决一些棘手的死锁问题。
Libevent本身可以不支持加锁,如果是这样,那程序使用上面的这些函数也是没有效果的。
Debugging lock usage 调试锁
Libevent提供一个可选的lock debugging功能,封装了使用锁的函数来捕获错误,错误包括:
- 释放一个没有获得的锁
- 重复加锁,当该锁不是递归锁
如果上面的错误发生,Libevent会因断言失败而退出。
void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
注意
这个函数必须在创建任何锁之前先调用。为了安全,当你设置线程函数之后马上调用它。
这个函数在2.0.4-alpha中创建。
调试event
有一些普通的错误可以被监测并向你报告,包括:
- 使用未初始化的event数据结构
- 尝试初始化一个未决的event数据结构
跟踪哪些event没有初始化需要消耗额外的cpu和内存,所以在你调试程序时才使用这个功能。
void event_enable_debug_mode(void);
这个函数必须在创建任何event_base
之前被先调用。
使用debug模式下,如果你的程序使用event_assign()
[不是event_new()
]来创建大量的event,你可能用光所有内存。 因为Libevent无法判断一个event_assign()
创建的event是否不再使用(但它能识别event_new()
创建的event是因为你可以调用event_free()
来让这个event失效)。为了避免耗光内存,你可以告诉Libevent这个event不再跟踪:
void event_debug_unassign(struct event *ev);
如果没有打开调试模式,调用这个函数则没有效果。
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);
}
检测Libevent的版本
新版本的Libevent能带来新特性和修复bugs,有时你会检测Libevent的版本,你能够:
- 检测已经安装的Libevent版本,有助你创建你的程序
- 显示Libevent版本来进行调试
- 检测Libevent版本你能够警告用户bugs,或者解决bugs
#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的话,那这些函数版本可能不一样。
你能够获得2种格式的版本信息:a)可以展示给用户的字符串 , b)4个字节的数字。 数字版本号中,第1个字节表示主版本,第2个字节表示小版本,第3个字节用于修复版本,以及最后一个字节表示发布状态(0用于正式版,非0表示正在开发中)。
比如,字符串的版本号2.0.1-alpha有对应的数字版本[02 00 01 00],或者0x02000100.
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;
}
释放全局数据结构
即使你释放了所有你创建的Libevent数据,仍然有小部分全局数据结构遗留下来。 事实上这并不是个问题,一旦进程退出, 进程使用的内存会被清理回收的。 但遗留这些数据会让一些调试工具误以为Libevent存在内存泄漏问题。如果你要确保Libevent释放所有内部Libevent库使用的全局数据结构,你可以调用:
void libevent_global_shutdown(void);
这个函数并不释放那些你通过Libevent申请的数据。如果退出前你要释放那些通过Libevent申请的数据,你需要自己释放events, event_bases, bufferevents等。
调用libevent_global_shutdown()
会让其他Libevent函数变得不可预测,但它可以是你程序里最后一个调用的Libevent函数。
– EOF –