LiEvent中文帮助文档--第5章【设置LibEvent库】
Libevent
快速可移植非阻塞式网络编程
修订历史
版本
日期
作者
备注
V1.0
2016-11-15
周勇
Libevent编程中文帮助文档
文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.
此外,本文档的源代码示例也是基于BSD的"3条款"或"修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:
英文:http://libevent.org/
中文:http://blog.csdn.net/zhouyongku/article/details/53431750
请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.
5.设置LibEvent库
LibEvent在进程中有一些影响整个库的全局设置.在你调用LibEvent库中任何一个部分之前都需要进行设置,否则libEvent将会进入不一致的状态.
5.1LibEvent日志消息
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);
为了覆盖LibEvent的日志行为,你需要自己编写满足event_log_cb格式的函数,然后将函数作为参数传入event_set_log_callback().无论什么时候只要LibEvent需要写一个日志,都会进入到你提供的日志函数.你需要让LibEvent日志回到默认功能的时候只需要再次调用event_set_log_callback()并且传一个NULL即可.
例子
#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回调函数中进行LibEvent的函数调用是不安全的.如果你想写一个用bufferevents去发送告警信息给一个网络socket的日志回调函数,你就可能会遇到非常奇怪和难以诊断的错误.在将来的版本中将会为一些函数移除这些限制.
通常调试日志都是禁用的,也都不会发送给日志回调函数,但是如果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>中,首先出现是在LibEvent的1.0c版本,而event_enable_debug_logging()是在LibEvent的2.1.1-alpha版本.
在LibEvent2.0.19之前兼容性需要注意的是EVENT_LOG_*宏在此前有下面的记录:_EVENT_LOG_DEBUG, _EVENT_LOG_MSG, _EVENT_LOG_WARN, and _EVENT_LOG_ERR.这些老的命名已经废弃不用,只会仅仅用于兼容LibEvent2.18及之前的版本,这些在将来的版本都会全部移除掉.
5.2处理致命错误
当LibEvent检测到一个不可恢复的致命错误(比如数据结构损坏),它的默认行为是调用exit()或abort()来退出当前运行的进程.这意味着有一个错误,要么在你的代码中,要么在LibEvent中.
如果你想让你的程序更从容地应对致命错误,你可以为LibEvent提供退出时候应该调用的函数,覆盖默认行为.
接口
typedef void ( * event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
要使用这些函数,你首先需要定义一个当LibEvent遇到错误时候需要调用的函数,并且将它传到event_set_fata_callback()函数.之后如果LibEvent遇到致命错误,它就会调用你提供的函数.
你的函数不可以控制返回到LibEvent,因为这样做可能会导致未定义行为发生,为了避免崩溃,LibEvent还是会退出.一旦你的程序被调用了,在你的函数中就不应该再调用别的LibEvent的函数.
这些函数被定义在<event2/event.h>,第一次出现在LibEvent2.0.3-alpha版本中.
5.3内存管理
默认情况下,LibEvent使用c语言库中提供的内存管理函数在堆上分配内存,你也可以替换malloc、realloc和free,让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));
例子
#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会使用这些函数分配返回给你的内存.
如果你已经替换了malloc和realloc函数并且想释放LibEvent分配的内存,那么就需要使用你替换的free函数来释放.
event_set_mem_functions()函数在<event2/event.h>中申明,首次出现是在LibEvent2.0.1alpha版本.
LibEvent可以被编译为event_set_mem_functions()禁用,如果这样,event_set_mem_functions()函数不会被编译和链接到程序.LibEvent2.0.2-alpha版本之后,你可以检查是否定义了EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED宏来检测event_set_mem_functions()函数是否存在.
5.4线程和锁
如果你写过多线程程序,你会知道在多线程中同一时间访问同一个数据通常是不安全的.
LibEvent的结构体在多线程一般有三种工作方法:
-
某些结构体在单线程内部是安全的,但在多线程中同时访问不再是安全的.
-
某些结构体是有可选的锁的,你需要告知LibEvent是否需要在多线程中使用每个对象.
某些结构体是一直锁住的,如果LibEvent在运行中支持锁,那么在多线程中用这些结构体是安全的.
为了LibEvent上锁,你需要告知LibEvent使用的是哪个锁函数,在调用任何LibEvent函数分配一个线程间共享的结构之前你就需要这样做.
如果你用的是pthreads库或者是windows的线程代码,那么你很幸运,那些预定义函数将会帮你把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
这两个函数都是成功返回0失败返回-1.
如果你需要使用不同的线程库,那么在此之前还有一些需要做,你需要用你的库定义函数去实现:
-
锁
-
上锁
-
解锁
-
分配锁
-
释放锁
-
条件
-
创建条件变量
-
释放条件变量
-
等待条件变量
-
信号/广播一个条件变量
-
线程
-
线程ID检测
使用 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:-仅用于读写锁,为读操作请求或释放锁.
-
EVTHREAD_WRITE:仅用于读写锁,为写操作请求或释放锁.
EVTHREAD_TRY:仅用于锁定,仅可请求的时候立刻请求锁定.
参数id_fn必须是一个返回无符号长整形的函数指针,用于标记调用函数的线程.在相同的线程中它一直会返回相同的值,不同的线程,调用它会返回不用的值.
evthread_condition_callbacks函数描述灰调函数相关条件变量.针对上述版本,lock_api_version必须设置为EVTHREAD_CONDITION_API_VERSION.alloc_function返回一个只想新的条件变量的指针,用0最为其参数.函数free释放条件变量持有的资源和存储.wait_function函数有三个参数:alloc_condition分配的条件、你提供的evthread_lock_callabcks.alloc函数分配的锁、可选的超时条件.无论什么时候函数调用锁都会被持有,函数必须释放锁,等到变量有信号,或者直到超时的时间已经流逝.wait_condition应该在错误时返回-1,条件变量授信时返回0,超时时返回1.返回之前,函数应该确定其再次持有锁.最后,signal_condition函数应该唤醒等待该条件变量的某个线程( broadcast参数为false)或者唤醒等待条件变量的所有线程(broadcast参数为true时).只有在持有与条件变量相关的锁的时候,才能够进行这些操作.关于条件变量的更多信息,请查看 pthreads的pthread_cond_*函数文档,或者 Windows的CONDITION_VARIABLE( Windows Vista新引入的)函数文档.
示例
关于使用这些函数的示例,请查看Libevent源代码发布版本中的evthread_pthread.c和evthread_win32.c文件.这些函数在<event2/thread.h>中声明,其中大多数在2.0.4-alpha版本中首次出现.2.0.1-alpha到2.0.3-alpha使用较老版本的锁函数.event_use_pthreads函数要求程序链接到event_pthreads库.条件变量函数是2.0.7-rc版本新引入的,用于解决某些棘手的死锁问题.可以创建禁止锁支持的 libevent.这时候已创建的使用上述线程相关函数的程序将不能运行.
5.5 调试锁的使用
为了辅助调试锁使用,LibEvent有一个"锁调试"特殊选项,它包裹锁了调用,以便获取到典型的锁错误,包括:
-
解锁但实际上并不能持有的锁.
-
重复锁定一个非递归的锁.
如果其中这些锁发生了错误,LibEvent将会伴随断言错误退出.
接口
void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
注意:这个函数必须在任何锁创建和使用之前调用,为了保证安全,在设置你的线程函数之后就调用.
这个函数首次以拼写错误的名称"evthread_enable_lock_debuging()."出现在LibEvent2.0.4-alpha版本,在2.1.2-alpha版本之后拼写固定为"evthread_enable_lock_debugging()",现在这两个名称都是支持的.
5.6调试事件使用
有一些常用的错误LibEvent会检测到并且报告给你,他们是:
-
把一个未初始化的event结构体当做已经初始化.
-
试图重新初始化一个未完成的event结构.
跟踪哪些event初始化了LibEvent需要额外的内存和CPU,所以在实际调试你的程序的时候你应该启用调试模式.
接口
void event_enable_debug_mode(void);
该函数必须在所有event_base创建之前调用.
当启用调试模式,如果你用event_assign()(不是event_new()函数 )创建了大量的events,你的程序可能会运行内存不足,这是因为LibEvent无法获知event_assign创建的event是否不再使用(可以调用event_free()通知event_new()创建的对象已经失效).如果你想避免运行内存不足的情况发生,你要明确地告诉LibEvent这些event都不再被分配.
接口
void event_debug_unassign(struct event* ev);
当启用调试模式的时候调用event_debug_unassign()将不再起效.
示例
#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程序都记录底层和后台的详细日志.这些日志包含但不限于下面所示:
-
事件添加
-
事件删除
-
平台特定的事件通知信息
这种特性不能通过API调用来关闭和开启,所有必须在开发人员创建程序的时候使用.
这些调试功能加进了LibEvent2.0.4-alpha版本.
5.7检查LibEvent的版本信息
新版本的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,这两个版本可能不同.
以获取两种格式的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.
示例:编译时检测
#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
}
示例:运行时检测
#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版本.
5.8释放LibEvent全局结构体
即便你释放了所有LibEvent分配的对象,任然会有一些全局分配的结构体保留,通常这都不是什么问题,当进程退出的时候他们都将被清理干净,但是这些残存的结构体会导致某些调试工具认为LibEvent有资源泄露.如果你确实需要确保LibEvent释放了所有库内部的全局数据结构,那么你可以调用:
接口
void libevent_global_shutdown(void);
这个函数不会释放任何libEvent函数返回给你的结构体,如果你想在退出的时候释放所有,那么你需要自己释放所有的events、event_bases、bufferevents等.
调用libevent_global_shutdown()函数将会使得别的LibEvent的函数产生不可预知的行为.除了程序调用了最后一个LibEvent的函数否则不要调用它.
这个函数声明在<event2/event.h>中,LibEvent2.1.1-alpha版本中出现.