设置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可以诊断并报告给你一些通用的错误,他们包括:

  1. 默认事件(event)已经初始化。
  2. 尝试重新初始化一个挂起的事件(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中引进。

0 0
原创粉丝点击