0 Preliminaries
1 设置 Libevent库
2 event_base
Libevent有一些整个库共享的全局设置,在调用库的任一部分之前都需要修改这些设置,否则有可能会出现不一致的状态。
- log日志
Libevent能够打印内部错误和警告信息,默认情况下这些信息都是通过标准输出,所以我们也可以复写我们自己的log函数。接口如下:
#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的log,只需要写一个上面的函数作为参数传入event_set_log_callback(),当Libevent要打印log时会将信息穿给我们自己写的函数,可以向event_set_log_callback()传入NULL使log以默认方式输出。看下面一个例子:
#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);
}
注意:在调用者自己写的event_log_cb callback函数里面调用Libevent函数是不安全的。比如在回调函数里面使用bufferevents向网络socket写警告信息,很有可能遇到奇怪而又难以诊断的问题,这个局限性问题在未来版本的Libevent中可能会被解决。默认debug logs不可用,可手动开启:
#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是开始所有log,以后的版本可能会支持更多选项。这些功能函数声明在“event2/event.h”。
- 处理致命错误
当Libevent感知到不可恢复的内部错误时,默认执行是调用exit() 或 abort()函数退出当前运行程序,因为这些错误意味着在你写的程序中或者Libevent库中存在bug。我们还可以像下面一样重写callback函数来处理致命错误:
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
上面的函数同样声明在“event2/event.h”,Libevent 2.0.3-alpha以后才有。需要注意的是,在我们自定义的callback调用函数里,不能调用Libevent的任何函数。
- 内存管理
默认情况,Libevent使用C内存管理方法从堆中分配内存,Libevent提供替换malloc, realloc, 和free的接口,这样你就可以设置自己想要使用的内存管理策略。接口如下所示,声明在“event2/event.h”, 第一次出现在Libevent 2.0.1-alpha.
void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
void *(*realloc_fn)(void *ptr, size_t sz),
void (*free_fn)(void *ptr))
例子如下:
#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结构体具有三种多线程工作方式:(1)本质上的单线程结构;(2)可选择加锁结构;(3)持续加锁结构;
想要实现加锁,需要告诉Libevent使用的加锁函数,如果是使用线程库或者本地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
然后需要调用Libevent的evthread_set_lock_callbacks和evthread_set_id_callback接口:
#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定义了加锁的callback函数和他们的功能,alloc函数返回特定类型的新锁;free函数释放特定类型锁持有的所有资源;lock函数申请对特定类型加锁,unlock函数解锁,这两个函数都是返回0值代表加锁成功,非零失败;对于上面这些函数的使用例子,可以参考文件evthread_pthread.c和evthread_win32.c。
- 查看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);
例子:Compile-time
#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
}
例子: Run-time
#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;
}
event_get_version()第一次出现在版本Libevent 1.0c,其他函数第一次出现在Libevent 2.0.1-alpha。
- 释放全局结构体
尽管已经释放了Libevent分配的所有对象,还是会有一些分配的全局结构体存在,虽然程序退出时它们也会被回收,但是对于一些调试工具而言会被误导为是Libevent库的弱链接资源,调用下面的接口函数可主动释放所有的全局结构体对象:
void libevent_global_shutdown(void);