http://www.wangafu.net/~nickm/libevent-book/Ref1_libsetup.html
配置libevent库
libevent有一些在整个进程中共享存在的全局的设置,这些设置会影响整个类库。
你必须在调用任何libevent类库之前设置好,不然的话,可能会有无法预料的错误。
libevent中的log消息
libevent可以记录底层的错误和警告日志。也可以记录调试日志,当然需要编译对应的版本。默认情况下,日志会写入到stderr。你可以用自己的函数重载它。
接口
#define EVENT_LOG_DEBUG 0 #define EVENT_LOG_MSG 1 #define EVENT_LOG_WARN 2 #define EVENT_LOG_ERR 3 #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想写log的时候,就会调用你传进去的函数。如果你想改回默认值,只需要再次调用 event_set_log_callback() ,传递 NULL 就可以了。
#include <event2/event.h> #include <stdio.h> static void discard_cb(int severity, const char *msg) { } 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; } fprintf(logfile "[%s] %s ", s, msg); } void suppress_logging(void) { event_set_log_callback(discard_cb); } void set_logfile(FILE *f) { logfile = f; event_set_log_callback(write_to_file_cb); }
注意事项
通过用户提供的回调 event_log_cb libevent的函数,有时候是不安全的。举例来说,如果我们的log函数中使用了 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> 中的函数,都是在1.0c的版本第一次出现的,除了 event_enable_debug_logging() ,这个是在2.1.1-alpha版本才添加的。
兼容性
在2.0.19-stable版本之前, EVENT_LOG_* 的前面有一个下划线: _EVENT_LOG_DEBUG _EVENT_LOG_MSG _EVENT_LOG_WARN 和 _EVENT_LOG_ERR 。老的名字不建议使用,除非我们使用2.0.18-stable或是更早版本的libevent。在将来的版本中也会被删除。
错误处理
当libevent遇到了不可修复的错误是,比如数据崩溃,默认情况下会调用 exit() 或 abort() 退出当前的程序。这些错误一般都表示在我们写的程序或是libevent中有bug。
你可以提供一个函数,当libevent退出前,调用我们的方法,进行一些出错处理。
接口
typedef void **event_fatal_cb)(int err); void event_set_fatal_callback(event_fatal_cb cb);
使用这个函数,我们需要先定义一个新的函数,然后通过 event_set_fatal_callback() 传递进去。当libevent触发错误时,就会回调我们提供的函数。我们的函数不能把控制权再交给libevent,不然的话会导致无法预料的错误。一旦我们错误处理的函数被调用后,就不可以再调用任何libevent的函数了。
这个函数定义在 <event2/event.h> ,第一次出现是在libevent 2.0.3-alpha版本。
内存管理
默认情况下,libevent用C语言标准库中的函数申请内存。你也可以使用自己提供的方法来代替 malloc realloc 和 free 。一般用于我们自己有一套更有效率的内存管理方案或是要检查内存泄漏的问题。
接口
void event_set_mem_functions(void *(*malloc_fn)(size_t sz), void *((relloc_fn)(void *ptr, size_t sz), void (*free_fn)(void *ptr));
这里有一个简单的例子,用我们的方法代替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申请内存的接口,确保你在调用任何libevent函数之前替换,不然有可能你用自己的函数释放了用C语言标准库申请的内存。
- 自己的申请内存的函数必须要返回申请的内存块,形式与C语言标准库的一样。
- 自己的替代 realloc 的方法必须能正确处理 realloc(NULL, sz) 的调用,相当于 malloc(sz) 的调用
- 自己的替代 realloc 的方法必须能正确处理 realloc(ptr, 0) 的调用,相当于 free(NULL)
- 自己的替代 free 的方法不需要处理 free(NULL)
- 自己的替代 malloc 的方法不需要处理 malloc(0)
- 必须要保证线程安全
- libevent会使用这些函数申请内存并返回给你。如果你已经用libevent申请了内存,又替换了内存管理的方法,可能你必须用自己的函数来释放libevent申请的内存了。
event_set_mem_functions() 定义在 <event2/event.h> 。第一次出现在2.0.1-alpha版本。
libevent编译的时候,可以禁用 event_set_mem_functions() 。在高于2.0.2-alpha版本的库中,可以通过宏 EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED 来判断 event_set_mem_functions 是否有效。
锁和多线程
写过多线程的都知道,多个线程访问同一个数据必须保证数据是安全的。
libevent的结构体在多线程下通常有三种工作方式
- 只在一个线程内部使用的数据,多线程访问是有问题的
- 可选的加锁,你可以告诉libevent这个数据是否在多线程下使用
- 总是加锁,这种方式在多线程下是安全的
必须在调用libevent的任何函数之前,设置好使用的锁的类型。
如果你是用的是pthread或是Windows平台下的线程函数,那么就很幸运了,libevent提供了宏定义用来选择使用哪种线程。
接口
#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 *);
这里有一些对于上面函数的介绍,就不做翻译了,因为我们很少用到这个功能:完全自己实现锁、临界变量。
调试锁
libevent提供了一些用于锁调试的功能,用于调试如下锁的错误:
- 释放了一个未获取到的锁
- 加锁了一个非递归锁
出现上面两个问题的时候,libevent都会退出报错。
接口
void evthread_enable_lock_debugging(void); #define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
这个函数必须在使用任何锁之前调用,为了安全起见,最好设置好线程后就直接调用。这个函数一开始写错了,后来新的版本更改了拼写错误。
调试事件
当使用事件的时候,libevent会通知一些错误:
- 使用了一个未初始化的事件
- 尝试初始化一个挂起的事件,也就是加入到libevent监听的事件
跟踪这些错误,需要额外的CPU和内存资源,所以仅仅在调试解决问题的时候才使用这些方法。
接口
void event_enable_debug_mode(void);
这个函数必须在所有的 event_base 创建之前调用
使用这个模式的时候,如果用 event_assign 创建了大量的event有可能会导致内存溢出,因为libevent并不知道通过 event_assign 创建的event什么时候不再使用了。通过 event_new() 创建的在调用 event_free() 的时候libevent可以知道event失效了。如果要避免这个问题,就需要我们明确的告诉libevent哪一个通过 event_assign 创建的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); }
释放全局的结构体
即使我们释放了所以自己申请的内存空间,还有几个全局的数据需要释放。这也不算很严重的错误:程序退出时,也会被释放掉。但是有可能是一些调试工具产生误解,以为libevent有内存泄露。如果确保libevent内部申请的数据都被释放了,可以调用
void libevent_global_shutdown(void);
这个函数并不会释放我们自己通过libevent申请的结构体,那些结构体需要我们自己释放。
调用这个后会是其他的libevent函数运行异常,确保你在最后一个libevent函数调用后再运行这个API。这个API是幂等的,也就是多次调用没有任何影响。