现在的位置: 首页 > 综合 > 正文

设置Libevent库

2014年10月05日 ⁄ 综合 ⁄ 共 11506字 ⁄ 字号 评论关闭



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_ERR

typedef 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   0xffffffffu

void 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 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:失败。

如果你想使用不同的线程库,你将有更多的工作需要去做。你需要实现以下内容:

  • 锁(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 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);
    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"
#endif

int 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中引进。

抱歉!评论已关闭.