设置Libevent库
来源:互联网 发布:网络教育能考四六级吗 编辑:程序博客网 时间:2024/05/03 09:58
Libevent有一些全局的设置共享给所有的程序。他影响整个库。你必须在使用Libevent任一库之前来设置这些变量,否则会导致Libevent状态的不一致。
Libevnet的日志消息
Libevent可以记录内部的错误和警告。如果编译添加了对日志记录的支持,你也可以记录调试信息。这些信息默认的被输出了标准错误输出。你可以提供自己的日志记录函数来覆盖原来的日志记录的方式。
接口
#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_ERRtypedef 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作为参数来设置使用原来的默认的日志记录方式。
示例(r1_01.c)
#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 = "err"; 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);}/* Recirect 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函数是不安全的。例如:如果你想写一个日志记录函数使用bufferevnets将信息发送到网络socket,可能会遇到奇怪的难以排除的bug。这个限制将会在将来的版本中的从一些函数中移除。
接口(re_snip_02.c)
#define EVENT_DBG_NONE 0#define EVENT_DBG_ALL 0xffffffffuvoid event_enable_debug_logging(ev_uint32_t which);
调试信息在大多数情况下是冗余的没有多大用处的。调用event_enable_debug_logging()用EVENT_DBG_NONE参数得到默认的设置,调用他用EVENT_DBG_ALL打开所有支持的调试信息。更多比较好的选项将会在将来的版本中被支持。
这些函数在中声明。除了event_enable_debug_logging()在Libevent2.1.1-alpha版本中首次出现外,其他的都在Libevent1.0c中首次出现。
兼容性注意
在Libevent2.0.19-stable版本之前,EVENT_LOG_*宏被定义为以下划线开头,例如:_EVENT_LOG_DEBUG, _EVENT_LOG_MSG, _EVENT_LOG_WARN和_EVENT_LOG_ERR。这些先前的宏是弃用的,你只应该在为了向前兼容Libevent2.0.18-stable和之前的版本时使用。他们将会在将来的版本中被移除。
处理致命的错误
当Libevent发现不可恢复的内部错误(像被损坏得数据结构),默认的处理方法是调用exit()或是abort()离开当前的工作进程。出现不可恢复的错误说明在你的程序中或是Libevent中出现了bug。
如果你想你的程序更加优雅的处理致命的错误你可以改变Libevent默认的处理方式,Libevent将调用你提供的函数来替代退出。
Interface
typedef void (*event_fatal_cb)(int err);void event_set_fatal_callback(event_fatal_cb cb);
为了使用上面功能,你首先要定义一个当发生致命的错误的时候可供Libevent调用的函数,然后将这个函数传给event_set_fatal_callback()。 在设置完成之后,如果发生了致命的错误,Libevent将会调用你提供的函数。
你的函数不应该将控制权返回给Libevent。如果这么做了可能会引发一些未知的错误,Libevent应该立即退出以避免程序down掉。一旦你提供的函数被调用,你不应该再调用Libevent其他的函数。
这些函数在中声明。首次出现在Libevent2.0.3-alpha版本中。
内存管理
Libevent默认使用标准的C库内存管理函数从堆上分配内存。你也可以使用通过提供malloc,realloc和free函数来使用其他的内存管理。如果你有更高效的内存分配器让Libevent使用,或者是你有内置指示器的内存分配器想让Libevent查找内存泄露,你可以使用自己的内存管理。
Interface
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 (r1_snip_03.c)
#include <event2/event.h>#include <sys/types.h>#include <stdlib.h>union alignment { size_t sz; void *ptr; double dbl;};#define ALIGNMENT sizeof(union alignment)#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);}
注意
- 替换内存管理函数将会影响所有的Libevent中调用malloc,resize, 或是free函数的函数。因此你得确定在你调用任何Libevent函数之前来替换这些函数。否则,Libevent就可能使用你的版本的free函数来释放由C标准版本malloc申请的空间。
- malloc和realloc函数要返回和标准C库同样校准(alignment)的内存地址
- realloc函数要正确处理realloc(NULL, sz)。
- realloc函数要正确处理realloc(ptr, 0)。
- free函数不需要处理free(NULL)。
- malloc函数不需要处理malloc(0)。
- 如果你在多线程中使用Libevent,你要确保你替换的函数是线程安全的。
- Libevent将会使用你提供的函数分配内存返回给你。因此如果你使用自己的内存管理函数替换了原来的函数,想要释放Libevent中函数申请并返回的内存,你将不的不使用你替换的函数来释放他们。
event_set_mem_functions()函数在中声明。最早出现在Libevent 2.0.1-alpha版本中。
Libevent可以在编译的时候设置禁止使用event_set_mem_functions。如果设置了,然后在程序中使用event_set_mem_functions()将不能编译或是链接。在Libevent2.0.1-alpha以及以后的版本,你可以通过宏EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED来判断event_set_mem_functions()是否存在。
锁和线程
当你知道你的程序中可能会使用到多线程,在多线程同时访问有些数据的时候不可能总是线程安全。Libevent数据结构通常可以使用以下三种方式在多线程中工作。
- 有些数据只能在单线程中使用:在同一时间超过一个线程使用就是不安全的。
- 有些数据可以选择上锁:你可以告诉Libevent的每个对象你是否需要在多线程中使用他们。
- 有些数据总是上锁的:如果运行支持lock的Libevent, 在多线程中使用他们是安全的。
为了在Libevnet中使用锁,你必须告诉Libevent使用哪个lock函数。你必须在调用任何Libevent函数申请你需要在多个线程中共享的数据之前告诉Libevent使用哪个lock函数。
你非常幸运如果你使用线程库或是本地的Window线程代码。Libevnet预定义函数将设置Libevent为你使用正确的线程或是Window函数。
Interface (r1_snip_04.c)
#ifndef WIN32int evthread_use_windows_threads(void);#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED#endif#ifdef _EVENT_HAVE_PTHREADSint evthread_use_pthreads(void);#define EVTHREAD_USE_PTHREADS_IMPLEMENTED#endif
以上两个函数返回0:成功,-1:失败。
如果你想使用不同的线程库,你将有更多的工作需要去做。你需要实现以下内容:
- 锁(Locks)
- 上锁
- 解锁
- 分配锁
- 销毁锁
- 条件(Condition)
- 条件变量创建
- 条件变量销毁
- 等待条件变量
- 发送信号/广播给条件变量
- 线程(Thread)
- 线程ID检查
然后你使用evthread_set_lock_callbacks和evthread_set_id_callback接口来告诉Libevent这些实现。
Interfase
#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 1struct 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); void (*signal_condition)(void *cond, int broadcast); void (*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函数必须释放所有由allocl申请的已定义的类型锁的资源。lock函数必须尝试获取一个已定义类型的的锁,返回0表示成功,返回非零表示失败。unlock函数解锁,返回0表示成功,非零失败。
推荐锁的类型:
0
普通的,不需要递归的锁。
EVTHREAD_LOCKTYPE_RECURSIVE
如果一个线程已经获取它,再次获取他的时候不会阻塞。只有等到所有的上锁都解锁之后其他的线程才能获取这个锁。
EVTHREAD_LOCKTYPE_READWRITE
允许多个读线程同时获取它, 但同一时刻只允许一个写线程获取它。写线程获取到之后其他读线程也不能获取到。
推荐的锁的模式:
EVTHREAD_READ
只对“读写锁”:为读获取或释放锁。
EVTHREAD_WRITE
只对“读写锁”:为写读取或释放锁。
EVTHREAD_TRY
只对“上锁”:获取锁如果锁可以立即获取。
id_fn参数必须是一个返回unsigned long类型表示正在调用这个函数的线程的线程标示符的的函数。它在相同的线程中返回的值必须一样,在同时执行的不同的线程中必须返回不恩给你的值。
evthread_condition_callbacks数据结构描述了跟条件变量相关的回调函数。在以上描述的版本中,lock_api_version字段必须设置为EVTHREAD_CONDITION_API_VERSION。alloc_condition函数必须返回一个条件变量的指针,它接受0为它的参数。free_condition函数必须释放由条件变量持有的资源和存贮空间。wait_condition接受三个参数:由alloc_condition申请的条件变量, 由evthread_lock_callbacks申请的锁,malloc函数和可选的超时时间。锁将会被持有无论什么时候他被调用,它必须释放锁,等待直到条件被触发或是超时。在它返回之前,他应该确保他再次持有锁。最后,singal_condition函数会唤醒一个等待条件变量的线程(如果广播被禁止的话)或唤醒所有等待该条件变量的线程(如果广播被允许的话)。它将被持有当锁和条件绑定。
更多关于条件变量的信息,请查看线程的pthread_cond_*或是windows的CONDITION_VARIABL函数相关的文档。
Example
For an example of how to use these functions, see evthread_pthread.c and evthread_win32.c in the Libevent source discribution.
上述的本节中的函数在中声明。他们中的大多数首次出现在Libevent 2.0.4-alpha版本中。Libevent 2.0.1-alpha版本到2.0.3-alpha版本中使用旧的接口来设置上锁函数。event_use_pthreads()函数需要你链接event_pthreads库。
条件变量相关的函数在Libevent2.0.7-rc版本中出现,他们被加进来解决难以解决的死锁的问题。
Libevent可以在编译的时候设置禁止锁,如果禁止使用锁,使用以上线程相关的函数的程序将不能正常运行。
调试锁的使用
为了帮助调试锁的使用,Libevent有一个可选的特性"lock debugging",包装锁的调用,为了获取典型的锁错误。包括:
- 解一个实际上没有上锁的锁
- 再次获取一个非递归锁的锁
如果以上其中任何一类错误发生,Libevent将会退出并输出响应的断言信息。
interface (r1_snip_06.c)
void evthread_enable_lock_debugging(void);#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
注意
这个函数必须在任何锁创建和使用之前调用。为了安全,尽量在设置了线程函数之后调用。
这个函数在Libevent 2.0.4-alpha版本中以错误的拼写“evthread_enable_lock_debuging()”出现。这个拼写错误在在2.1.2-alpha版本中修改为evthread_enable_lock_debugging(),目前两种拼写都是支持的。
调试事件的使用
在使用事件时,Libevent可以诊断并报告给你一些通用的错误,他们包括:
- 默认事件(event)已经初始化。
- 尝试重新初始化一个挂起的事件(event)。
跟踪事件(event)是否初始化,Libevent需要使用额外的内存和CPU。所以如果你想精确的debug你的程序时你应该使用调试模式。
interface
void event_enable_debug_mode(void);
该函数必须在event_base创建之前调用。
当使用debug模式时,如果你使用event_assign()[而不是使用event_new()]创建了大量的事件(event)的话,有可能会内存溢出,这是因为Libevent无法知道使用event_assign创建的事件(event)已经不在使用。(通过调用event_free()可以告诉Libevent使用event_new()创建的事件已经不合法了。)如果你想在debug模式下内存溢出,你可以精确的告诉Libevent事件(event)不当做分配的事件(assigned event)。
interface
void event_debug_unassign(struct event *ev)
不在debug模式下调用event_debug_unassign()没有影响。
example (r1_02.c)
#include <event2/event.h>#include <event2/event_struct.h>#include <stdlib.h>void cb(evutil_socket_t fd, short what, void *ptr){ struct event *ev = ptr; if(ev){ event_debug_unassign(ev); }}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; 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);}
详细的事件(event)调试只能在编译的时候使用CFLAG环境变量"-DUSE_DEBUG"来开启。当设置了这个标志,任何编译违反Libevent的都会输出,这些信息包括但并不仅限以下:
- 事件的添加
- 事件的删除
- 平台相关的事件通知信息
这个特性不能通过API来设置,它必须在编译时设定。这些debug函数在Libevent2.0.3-alpha中添加。
检测Libevent的版本
新版本的Libevent可能会添加新的特性和解决一些bug。有时候你想获取探测Libevent的版本用来:
- 探测当前安装的Libevent的版本是不是最好的版本来编译你的程序。
- debug时显示Libevent的版本。
- 通过探测Libevent的版本来警告一些bug,或者是绕过他们。
interface (r1_snip_07.c)
#define LIBEVENT_VERSION_NUMBER 0x02000300#define LIBEVENT_VERSION "2.0.3-alph"const char *event_get_version(void);ev_uint32_t event_get_version_number(void);
以上的宏可用于Libevent库的版本编译;函数返回运行时的版本。注意,如果你动态绑定Libevent,版本可能有些不同。
你可以得到Libevent 版本的两种形式:1.字符串方便显示给用户,2.4字节的整数方便数字比较。数字形式用高字节为主要版本,第二个字节为次要版本,第三个字节为补丁的版本,最低字节用力啊显示发布的状态(0是正式发布,非零开发的版本)。
因此,发布的Libevent 2.0.1-alpha的版本号为[02 00 01 00] 或0x02000100。一个介于2.0.1-alpha和2.0.2-alpha之间的开发版本的版本号可能为[20 00 01 08]或0x02000108。
Example: Compile-time checks (r1_03.c)
#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"#endifint make_sandwich(void){#if LIBEVENT_VERSION_NUMBER >= 0x06000500 evutil_make_me_a_sandwich(); return 0;#else return -1;#endif;}
Example: Run-time checks (r1_04.c)
#include <event2/event.h>#include <string.h>int check_for_old_version(void){ const char *v = event_get_version(); 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申请的结构,但是还会余留一些全局的结构,这通常不是问题,一旦你的进程退出,他们将会自动释放。但是有这些结构会迷惑一些调试工具认为Libevent会内存泄露,如果你想确认Libevent已经释放了所有内部库全局结构,你可以调用:
interface
void libevent_global_shutdown(void);
该函数不释放任何你使用Libevent函数返回的数据结构. 如果你想在退出前释放所有的结构,你要释放所有的events, event_base, bufferevents和你自己定义的。调用libevent_global_shutdown()将会产生不可预料的结果,不要调用他,除非在程序的最后调用他。其中一个异常就是libevent_global_shutdown()在调用之后再一次调用是可以的。
该函数在中声明。在Libevent 2.1.1-alpha中引进。
- 设置Libevent库
- 设置Libevent库
- libevent学习笔记 -1 设置 Libevent库
- libevent 第一章 设置libevent
- 设置libevent
- LibEvent中文帮助文档--第5章【设置LibEvent库】
- 二、 设置libevent
- Libevent:2设置
- Libevent参考手册第一章:设置libevent(一)
- Libevent参考手册第一章:设置libevent(二)
- Libevent参考手册:设置libevent(一)
- Libevent参考手册:设置libevent(二)
- Libevent参考手册第一章:设置libevent (三)
- Libevent参考手册第一章:设置libevent(一)
- libevent库
- libevent库
- libevent简单应用:设置定时器
- libevent 第2章 配置LIBEVENT 库
- 读《一课经济学》18章有感,中国汽车使用的公平
- R语言生成时间数列
- linux解压命令
- 攻陷三次元! 二次元动漫的进击之路
- Android:TabHost
- 设置Libevent库
- DOM LEVEL 1 中的那些事儿[总结篇-下]
- MIPI DSI协议介绍
- Socket 连接异常 之"由于目标机器积极拒绝,无法连接" 的诊断
- 深入理解Java:注解(Annotation)自定义注解入门
- 如何在Eclipse下查看JDK源代码
- SVN配置方法
- libevent库1.4升级到2.0时无法flush的解决办法(互相踢下线)
- 《从零开始学Swift》学习笔记(Day 56)—— Swift编码规范之命名规范