libevent 有一些被整个进程共享的、影响整个库的全局设置。

必须在调用libevent 库的任何其他部分之前修改这些设置,否则,libevent 会进入不一致的状态。

提示:本章节部分内容转自《libevent中文手册(中文版)》

10.1 日志消息回调设置

libevent 可以记录内部错误和警告。如果编译进日志支持,还会记录调试信息。默认配置下
这些信息被写到stderr。通过提供定制的日志函数可以覆盖默认行为。

  1. #define EVENT_LOG_DEBUG 0
  2. #define EVENT_LOG_MSG 1
  3. #define EVENT_LOG_WARN 2
  4. #define EVENT_LOG_ERR 3
  5. /* Deprecated; see note at the end of this section */
  6. #define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
  7. #define _EVENT_LOG_MSG EVENT_LOG_MSG
  8. #define _EVENT_LOG_WARN EVENT_LOG_WARN
  9. #define _EVENT_LOG_ERR EVENT_LOG_ERR
  10. typedef void (*event_log_cb)(int severity, const char *msg);
  11. void event_set_log_callback(event_log_cb cb);

要覆盖libevent 的日志行为,编写匹配event_log_cb 签名的定制函数,将其作为参数传递
给event_set_log_callback()。

随后libevent 在日志信息的时候,将会把信息传递给你提供的函数。再次调用event_set_log_callback(),传递参数NULL,就可以恢复默认行为。

实例

  1. #include <event2/event.h>
  2. #include <stdio.h>
  3. static void discard_cb(int severity, const char *msg)
  4. {
  5. /* This callback does nothing. */
  6. }
  7. static FILE *logfile = NULL;
  8. static void write_to_file_cb(int severity, const char *msg)
  9. {
  10. const char *s;
  11. if (!logfile)
  12. return;
  13. switch (severity) {
  14. case _EVENT_LOG_DEBUG: s = "debug"; break;
  15. case _EVENT_LOG_MSG: s = "msg"; break;
  16. case _EVENT_LOG_WARN: s = "warn"; break;
  17. case _EVENT_LOG_ERR: s = "error"; break;
  18. default: s = "?"; break; /* never reached */
  19. }
  20. fprintf(logfile, "[%s] %s\n", s, msg);
  21. }
  22. /* Turn off all logging from Libevent. */
  23. void suppress_logging(void)
  24. {
  25. event_set_log_callback(discard_cb);
  26. }
  27. /* Redirect all Libevent log messages to the C stdio file 'f'. */
  28. void set_logfile(FILE *f)
  29. {
  30. logfile = f;
  31. event_set_log_callback(write_to_file_cb);
  32. }

在用户提供的event_log_cb 回调函数中调用libevent 函数是不安全的。

比如说,如果试图编写一个使用bufferevent 将警告信息发送给某个套接字的日志回调函数,可能会遇到奇怪
而难以诊断的bug。未来版本libevent 的某些函数可能会移除这个限制。

这个函数在中声明,在libevent 1.0c 版本中首次出现。

10.2 致命错误回调设置

libevent 在检测到不可恢复的内部错误时的默认行为是调用exit()或者abort(),退出正在运行的进程。这类错误通常意味着某处有bug:要么在你的代码中,要么在libevent 中。

如果希望更优雅地处理致命错误,可以为libevent 提供在退出时应该调用的函数,覆盖默认
行为。

  1. typedef void (*event_fatal_cb)(int err);
  2. void event_set_fatal_callback(event_fatal_cb cb);

要使用这些函数,首先定义libevent 在遇到致命错误时应该调用的函数,将其传递给
event_set_fatal_callback()。

随后libevent 在遇到致命错误时将调用你提供的函数。
你的函数不应该将控制返回到libevent:这样做可能导致不确定的行为。

为了避免崩溃,libevent 还是会退出。你的函数被不应该调用其它libevent 函数。
这些函数声明在中,在libevent 2.0.3-alpha 版本中首次出现。

10.3 内存管理回调设置

默认情况下,libevent 使用C 库的内存管理函数在堆上分配内存。

通过提供malloc、realloc和free 的替代函数,可以让libevent 使用其他的内存管理器。

希望libevent 使用一个更高效的分配器时;或者希望libevent 使用一个工具分配器,以便检查内存泄漏时,可能需要这样做。

  1. void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
  2. void *(*realloc_fn)(void *ptr, size_t sz),
  3. void (*free_fn)(void *ptr));

这里有个替换libevent 分配器函数的示例,它可以计算已经分配的字节数。

实际应用中可能
需要添加锁,以避免运行在多个线程中时发生错误。

实例

  1. #include <event2/event.h>
  2. #include <sys/types.h>
  3. #include <stdlib.h>
  4. /* This union's purpose is to be as big as the largest of all the
  5. * types it contains. */
  6. union alignment {
  7. size_t sz;
  8. void *ptr;
  9. double dbl;
  10. };
  11. /* We need to make sure that everything we return is on the right
  12. alignment to hold anything, including a double. */
  13. #define ALIGNMENT sizeof(union alignment)
  14. /* We need to do this cast-to-char* trick on our pointers to adjust
  15. them; doing arithmetic on a void* is not standard. */
  16. #define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
  17. #define INPTR(ptr) (((char*)ptr)-ALIGNMENT)
  18. static size_t total_allocated = 0;
  19. static void *replacement_malloc(size_t sz)
  20. {
  21. void *chunk = malloc(sz + ALIGNMENT);
  22. if (!chunk) return chunk;
  23. total_allocated += sz;
  24. *(size_t*)chunk = sz;
  25. return OUTPTR(chunk);
  26. }
  27. static void *replacement_realloc(void *ptr, size_t sz)
  28. {
  29. size_t old_size = 0;
  30. if (ptr) {
  31. ptr = INPTR(ptr);
  32. old_size = *(size_t*)ptr;
  33. }
  34. ptr = realloc(ptr, sz + ALIGNMENT);
  35. if (!ptr)
  36. return NULL;
  37. *(size_t*)ptr = sz;
  38. total_allocated = total_allocated - old_size + sz;
  39. return OUTPTR(ptr);
  40. }
  41. static void replacement_free(void *ptr)
  42. {
  43. ptr = INPTR(ptr);
  44. total_allocated -= *(size_t*)ptr;
  45. free(ptr);
  46. }
  47. void start_counting_bytes(void)
  48. {
  49. event_set_mem_functions(replacement_malloc,
  50. replacement_realloc,
  51. replacement_free);
  52. }

注意

  • 替换内存管理函数影响libevent 随后的所有分配、调整大小和释放内存操作。所以,必
    须保证在调用任何其他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 函数声明在中,在libevent 2.0.1-alpha 版本中
首次出现。

可以在禁止event_set_mem_functions 函数的配置下编译libevent 。这时候使用
event_set_mem_functions 将不会编译或者链接。

在2.0.2-alpha 及以后版本中,可以通过检查是否定义了EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED 宏来确定event_set_mem_functions 函数是否存在。

10.4 锁和线程的设置

编写多线程程序的时候,在多个线程中同时访问同样的数据并不总是安全的。

libevent 的结构体在多线程下通常有三种工作方式:

  • 某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
  • 某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。
  • 某些结构体总是锁定的 :如果 libevent 在支持锁的配置下运行 ,在多个线程中使用它们 总是安全的。

为获取锁,在调用分配需要在多个线程间共享的结构体的 libevent 函数之前,必须告 知 libevent 使用哪个锁函数。

如果使用 pthreads 库,或者使用 Windows 本地线程代码,那么你是幸运的:已经有设 置 libevent 使用正确的 pthreads 或者 Windows 函数的预定义函数。

接口

  1. #ifdef WIN32
  2. int evthread_use_windows_threads(void);
  3. #define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
  4. #endif
  5. #ifdef _EVENT_HAVE_PTHREADS
  6. int evthread_use_pthreads(void);
  7. #define EVTHREAD_USE_PTHREADS_IMPLEMENTED
  8. #endif

这些函数在成功时都返回 0,失败时返回 -1。

如果使用不同的线程库,则需要一些额外的工作,必须使用你的线程库来定义函数去实现:

  • 锁定
  • 解锁
  • 分配锁
  • 析构锁
  • 条件变量
  • 创建条件变量
  • 析构条件变量
  • 等待条件变量
  • 触发/广播某条件变量
  • 线程
  • 线程ID检测

使用 evthread_set_lock_callbacks 和 evthread_set_id_callback 接口告知 libevent 这些函数。

接口

  1. #define EVTHREAD_WRITE 0x04
  2. #define EVTHREAD_READ 0x08
  3. #define EVTHREAD_TRY 0x10
  4. #define EVTHREAD_LOCKTYPE_RECURSIVE 1
  5. #define EVTHREAD_LOCKTYPE_READWRITE 2
  6. #define EVTHREAD_LOCK_API_VERSION 1
  7. struct evthread_lock_callbacks {
  8. int lock_api_version;
  9. unsigned supported_locktypes;
  10. void *(*alloc)(unsigned locktype);
  11. void (*free)(void *lock, unsigned locktype);
  12. int (*lock)(unsigned mode, void *lock);
  13. int (*unlock)(unsigned mode, void *lock);
  14. };
  15. int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);
  16. void evthread_set_id_callback(unsigned long (*id_fn)(void));
  17. struct evthread_condition_callbacks {
  18. int condition_api_version;
  19. void *(*alloc_condition)(unsigned condtype);
  20. void (*free_condition)(void *cond);
  21. int (*signal_condition)(void *cond, int broadcast);
  22. int (*wait_condition)(void *cond, void *lock,
  23. const struct timeval *timeout);
  24. };
  25. int evthread_set_condition_callbacks(
  26. const struct evthread_condition_callbacks *);

evthread_lock_callbacks 结构体描述的锁回调函数及其能力。对于上述版本,

lockapi_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 :可以让多个线程同时因为读而持有它 ,但是 任何时刻只有一个线程因为写而持有它。写操作排斥所有读操作。

10.4.1 可识别的锁模式有:

  • EVTHREAD_READ :仅用于读写锁:为读操作请求或者释放锁
  • EVTHREAD_WRITE :仅用于读写锁:为写操作请求或者释放锁
  • EVTHREAD_TRY :仅用于锁定:仅在可以立刻锁定的时候才请求锁定

id_fn 参数必须是一个函数,它返回一个无符号长整数,标识调用此函数的线程。对于相同 线程,这个函数应该总是返回同样的值 ;而对于同时调用该函数的不同线程 ,必须返回不同 的值。

vthread_condition_callbacks 结构体描述了与条件变量相关的回调函数。对于上述版本 , condition_api_version 字段必须设置为 EVTHREAD_CONDITION_API_VERSION 。 alloc_condition 函数必须返回到新条件变量的指针 。它接受0作为其参数。free_condition 函 数必须释放条件变量持有的存储器和资源。 wait_condition 函数要求三个参数:一个 由 alloc_condition 分配的条件变量 ,一个由你提供的 evthread_lock_callbacks.alloc 函数分配 的锁,以及一个可选的超时值 。调用本函数时 ,必须已经持有参数指定的锁 ;本函数应该释 放指定的锁,等待条件变量成为授信状态,或者直到指定的超时时间已经流逝(可选 )。 wait_condition 应该在错误时返回-1,条件变量授信时返回0,超时时返回1。返回之前,函 数应该确定其再次持有锁。最后, signal_condition 函数应该唤醒等待该条件变量的某个线 程(broadcast 参数为 false 时),或者唤醒等待条件变量的所有线程(broadcast 参数为 true 时)。只有在持有与条件变量相关的锁的时候,才能够进行这些操作。

关于条件变量的更多信息,请查看 pthreads 的 pthreadcond*函数文档,或者 Windows 的 CONDITION_VARIABLE(Windows Vista 新引入的)函数文档。

实例:

  1. 关于使用这些函数的示例,
  2. 请查看 Libevent 源代码发布版本中的
  3. evthread_pthread.c evthread_win32.c 文件。

这些函数在 中声明,其中大多数在 2.0.4-alpha 版本中首次出现。 2.0.1-alpha 到2.0.3-alpha 使用较老版本的锁函数 。event_use_pthreads 函数要求程序链接 到 event_pthreads 库。

条件变量函数是2.0.7-rc 版本新引入的,用于解决某些棘手的死锁问题。

可以创建禁止锁支持的libevent。这时候已创建的使用上述线程相关函数的程序将不能运行。

10.4.2 调试做的使用

为帮助调试锁的使用,libevent 有一个可选的“锁调试”特征。这个特征包装了锁调用,以便捕获典型的锁错误,包括:

  • 解锁并没有持有的锁
  • 重新锁定一个非递归锁

如果发生这些错误中的某一个, libevent 将给出断言失败并且退出。

  1. void event_enable_debug_mode(void);

必须在创建或者使用任何锁之前调用这个函数。为安全起见,请在设置完线程函数后立即调用这个函数。

10.5 调试事件的使用

libevent 可以检测使用事件时的一些常见错误并且进行报告。这些错误包括:

  • 将未初始化的 event 结构体当作已经初始化的
  • 试图重新初始化未决的 event 结构体

跟踪哪些事件已经初始化需要使用额外的内存和处理器时间 ,所以只应该在真正调试程序的 时候才启用调试模式。

  1. void event_enable_debug_mode(void);

必须在创建任何 event_base 之前调用这个函数。

如果在调试模式下使用大量由 event_assign(而不是 event_new)创建的事件,程序可能 会耗尽内存,这是因为没有方式可以告知 libevent 由 event_assign 创建的事件不会再被使 用了(可以调用 event_free 告知由 event_new 创建的事件已经无效了 )。如果想在调试时 避免耗尽内存,可以显式告知 libevent 这些事件不再被当作已分配的了:

  1. void event_debug_unassign(struct event *ev);

没有启用调试的时候调用 event_debug_unassign 没有效果。

实例

  1. #include <event2/event.h>
  2. #include <event2/event_struct.h>
  3. #include <stdlib.h>
  4. void cb(evutil_socket_t fd, short what, void *ptr)
  5. {
  6. /* We pass 'NULL' as the callback pointer for the heap allocated
  7. * event, and we pass the event itself as the callback pointer
  8. * for the stack-allocated event. */
  9. struct event *ev = ptr;
  10. if (ev)
  11. event_debug_unassign(ev);
  12. }
  13. /* Here's a simple mainloop that waits until fd1 and fd2 are both
  14. * ready to read. */
  15. void mainloop(evutil_socket_t fd1, evutil_socket_t fd2, int debug_mode)
  16. {
  17. struct event_base *base;
  18. struct event event_on_stack, *event_on_heap;
  19. if (debug_mode)
  20. event_enable_debug_mode();
  21. base = event_base_new();
  22. event_on_heap = event_new(base, fd1, EV_READ, cb, NULL);
  23. event_assign(&event_on_stack, base, fd2, EV_READ, cb, &event_on_stack);
  24. event_add(event_on_heap, NULL);
  25. event_add(&event_on_stack, NULL);
  26. event_base_dispatch(base);
  27. event_free(event_on_heap);
  28. event_base_free(base);
  29. }