Libevent:2设置

         Libevent有一些整个进程共享的全局设置。这些设置会影响到整个的库。因此必须在调用Libevent其他函数之前进行设置,否则,LIbevent就会陷入不一致的状态。

 

一:Libevent中的日志信息

         Libevent可以记录内部的error和warning信息,而且如果在编译时设置的话,它还可以记录debug消息。默认情况下,这些信息都会写到stderr中。可以通过提供自己的日志函数来改变该行为。

#define  EVENT_LOG_DEBUG  0

#define  EVENT_LOG_MSG   1

#define  EVENT_LOG_WARN  2

#define  EVENT_LOG_ERR   3

 

/* Deprecated; see note at theend 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);

         如果想要改变Libevent默认的日志行为,需要编写自己的日志函数,该日志函数要符合event_log_cb原型,并且将该函数作为参数传入到event_set_log_callback()。当Libevent需要记录日志的时候,会将日志信息传入到你提供的函数中。如果想恢复到Libevent的默认日志行为,则只需要以NULL为参数调用event_set_log_callback()即可

 

实例:

#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 = "error"; 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);

}

 

/* Redirect 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回调函数中调用Libevent 函数是不安全的。比如,在自己写的日志回调函数中,使用bufferevents将warning信息发送到网络上,那么很可能会遇见奇怪且难以调试的bug。未来Libevent版本中,针对某些函数有可能会去除该限制。

 

         通常,debug级别的日志是禁用的,并且不会发送到日志回调函数中。如果在构建Libevent时支持的话,可以手动启用。

#define EVENT_DBG_NONE  0

#define EVENT_DBG_ALL      0xffffffffu

 

void  event_enable_debug_logging(ev_uint32_t which);

         在多数环境下,debug信息是冗长且无用的。使用EVENT_DBG_NONE参数调用event_enable_debug_logging() 可以得到默认行为;使用EVENT_DBG_ALL调用该函数,可以打开所有支持的debug日志。未来版本中可能会支持更加精细的选项。这些函数在<event2/event.h>声明。

 

         兼容性:在Libevent2.0.19-stable版本之前,EVENT_LOG_*这样的宏名称是以下划线开头的,比如_EVENT_LOG_DEBUG,_EVENT_LOG_MSG, _EVENT_LOG_WARN和_EVENT_LOG_ERR。这些古老的名字已经废弃,而且只能在Libevent 2.0.18-stable之前的版本中使用。未来版本中,他们可能会被移除。

 

二:处理致命错误

         当Libevent遇到一个无法恢复的内部错误的时候,它的默认行为是调用exit()或abort()退出当前运行的进程。这些错误意味着程序存在bug,要么在程序的代码中,要么在Libevent本身。

         如果希望应用进程更加优雅的处理致命错误的话,可以改变Libevent的这种默认行为。通过提供另一个函数以供Libevent调用,替代退出这种默认行为。

typedef  void (*event_fatal_cb)(int  err);

void  event_set_fatal_callback(event_fatal_cb  cb);

         首先定义一个新的函数,该函数在Libevent遇到致命错误的时候会被调用,然后将新定义的函数作为参数传给event_set_fatal_callback()函数。之后,当Libevent遇到致命错误时,将会调用你提供的新函数。

 

         注意:替代函数不要将控制返回到Libevent,否则会遇到非定义的行为,而且Libevent会退出。所以一旦你的函数被调用,就不要再次调用任何其他的Libevent函数。

 

三:内存管理

         默认情况下,Libevent使用C库提供的内存管理函数从堆上分配内存。你可以提供自己的内存管理函数以供Libevent使用,从而替代malloc, realloc和free。如果你有更有效的内存分配器,或者有可以检测内存泄露的内存分配器,你就可以这样做。

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分配函数的例子。在实际环境中,需要加上锁机制,以防止在多线程环境中遇到错误。

#include <event2/event.h>

#include <sys/types.h>

#include <stdlib.h>

 

/* This union's purpose is tobe 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 adouble. */

#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 notstandard. */

#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);

}

         注意:

         1:替换内存管理函数,将会影响到Libevent中所有内存分配,调整大小和释放内存操作。因此,需要在调用任何其他Libevent函数之前,进行这种替换。否则的话,Libevent将会使用你提供的free函数,来释放由C库的malloc函数申请的空间。

         2:你的malloc和realloc函数应该同C库返回的内存块一样,具有同样的内存地址对齐特性。

         3:你的realloc函数需要正确的处理realloc(NULL,sz)(也就是将其当做malloc(sz)处理)。

         4:你的realloc函数需要正确的处理realloc(ptr,0)(也就是将其当做free(ptr)处理)。

         5:你的free函数无需处理free(Null)。

         6:你的malloc函数,无需处理malloc(0)。

         7:如果在多线程环境中使用Libevent的话,需要保证你的内存管理函数是线程安全的。

         8:如果替代了Libevent内存管理函数,那么Libevent将会使用替代函数来分配内存,所以,应该使用free的替代版本来释放由Libevent返回的内存。

 

         event_set_mem_functions()函数在<event2/event.h>声明。

         Libevent可以在构建时禁用event_set_mem_functions()函数。如果禁用的话,那么使用event_set_mem_functions的代码将不会编译或链接。在Libevent2.0.2-alpha以及之后的版本中,可以通过检查宏定义EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED,来判断函数event_set_mem_functions是否被禁用。

 

四:线程和锁

         如你所知,同一时刻,多个线程访问同一个数据是不安全的。多线程环境下,Libevent的结构体有三种工作方式:

         一些结构体是单线程使用的:多线程同时使用是不安全的;

         一些结构体带有可选的锁:针对这种结构体,你可以告诉Libevent,是否会需要多个线程同时访问它

         一些结构体是带有强制锁:如果Libevent支持锁机制的话,那么这些结构体永远是线程安全的。

        

         为了获得Libevent中的锁机制,必须在调用任何分配多线程共享的结构体的函数之前,告诉Libevent使用哪些锁函数。

         如果使用pthreads库,或者使用原有的Windows多线程代码,那么已经有设置好的libevent预定义函数,能够正确的使用pthreads或者Windows函数。

#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, unsignedlocktype);

       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 *);

         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函数必须能够释放特定类型的锁的所有资源。lock函数用来以特定的模式获得锁,返回0表示成功,非0表示失败。unlock函数用来解锁,返回0表示成功,非0表示失败。

 

         锁类型包括:

         0:常规的、非递归锁

         EVTHREAD_LOCKTYPE_RECURSIVE:递归锁,允许同一个线程对其多次加锁,只有当前加锁的线程经过同样次数的解锁之后,其他线程才能够获得锁。

         EVTHREAD_LOCKTYPE_READWRITE:读写锁,允许多个线程同时占有读模式的读写锁,但是同一时刻,只能有一个线程占有写模式的读写锁。一个写线程会阻塞所有读线程。

 

         锁机制包括:

         EVTHREAD_READ:读写锁特有,以读模式获得或释放读写锁

         EVTHREAD_WRITE:读写锁特有,以写模式获得或释放读写锁

         EVTHREAD_TRY:只有该锁可以立即获得时,才可以加锁。

 

         id_fn参数是一个函数,该函数返回一个无符号长整型,该长整型用来区分调用该函数的线程。同一个线程必须返回同样的整数、同一时刻执行的不同线程必须返回不同的整数。

 

         evthread_condition_callbacks结构体描述了条件变量的特性。lock_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的话,所有等待条件变量的线程都会唤醒。只有在对与该条件变量相关的锁进行加锁之后,才能进行这些操作。

 

         实例:参见evthread_pthread.c文件和evthread_win32.c文件

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

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

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

 

五:锁调试的使用

         为了能够对锁的使用进行调试,Libevent提供了“锁调试”的特性,它对锁调用进行了封装,从而可以捕捉到典型的锁错误,包括:解锁一个并没有加锁的锁;对一个非递归锁重新加锁。

如果发生了锁错误,Libevent将会以一个断言失败而退出。

void  evthread_enable_lock_debugging(void);

#define  evthread_enable_lock_debuging()  evthread_enable_lock_debugging()

         该函数必须在任何锁创建和使用之前进行调用。安全起见,在设置线程函数之后调用。

 

六:事件调试的使用

         在使用events时,Libevent可以检测并报告一些一般性的错误,包括:将一个未初始化的event当做已经初始化的event使用;对处于pending状态的event重新初始化。

         因为跟踪哪个event被初始化需要额外的内存和CPU,所以应该仅调试程序时才使能调试模式。

void  event_enable_debug_mode(void);

         该函数必须在任何event_base创建之前调用

         在debug模式下,如果程序用event_assign(不是event_new)创建了大量的events,那么有可能会将内存耗尽。这是因为Libevent没有办法知道由event_assign创建的event何时不再使用(对于event_new创建的event,当你调用event_free时,该event就会变为无效的了)。如果想要避免在调试模式下耗尽内存,可以明确地告诉Libevent,该event已经无效了:

void  event_debug_unassign(structevent *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);

}

         事件调试这种特性,只有在编译时使用"-DUSE_DEBUG"CFLAGS环境变量才能进行使能。使用该标识后,任何编译连接Libevent的程序将会输出非常详尽的日志信息。这些日志包括但不限于:添加event,删除event,特定平台的事件通知信息。

         这种特性不能通过API的调用进行开启或禁用。这些调试功能在Libevent2.0.4-alpha之后加入。

 

七:检测Libevent版本

         如果希望检测当前使用的Libevent版本,可以调试时打印Libevent的版本信息;

#define  LIBEVENT_VERSION_NUMBER  0x02000300

#define  LIBEVENT_VERSION  "2.0.3-alpha"

const char*  event_get_version(void);

ev_uint32_t  event_get_version_number(void);

         这些宏提供了Libevent库的编译时版本;而函数返回运行版本。注意,如果你是动态链接到Libevent的话,这些版本有可能是不同的。

         可以以两种格式得到Libevent的版本:适于展现给用户的字符串形式和适于进行数字比较的4字节整型形式。

整型形式中,使用高字节代表主版本,第二个字节代表次版本,第三个字节代表补丁版本,最后一个字节表示发布状态,0表示发布版本,非0表示给定发布版本之后的开发系列版本。因此,对于2.0.1-alpha发布版本的Libevent,它的版本号是[02 00 01 00],或者0x02000100。介于2.0.1-alpha 和 2.0.2-alpha之间的开发版本可能的版本号是[02 00 01 08],或是 0x02000108.

#include  <event2/event.h>

 

#if  !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER < 0x02000100

#error "This version ofLibevent is not supported; Get 2.0.1-alpha or later."

#endif

 

int

make_sandwich(void)

{

        /* Let's suppose that Libevent 6.0.5introduces a make-me-a

           sandwich function. */

#if  LIBEVENT_VERSION_NUMBER>= 0x06000500

        evutil_make_me_a_sandwich();

        return 0;

#else

        return -1;

#endif

}

Example: Run-time checks

#include <event2/event.h>

#include <string.h>

 

int

check_for_old_version(void)

{

    const char *v = event_get_version();

    /* This is a dumb way to do it, but it isthe only thing that works

       before Libevent 2.0. */

    if (!strncmp(v, "0.", 2) ||

        !strncmp(v, "1.1", 3) ||

        !strncmp(v, "1.2", 3) ||

        !strncmp(v, "1.3", 3)) {

 

        printf("Your version of Libeventis 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;

}

         这些宏和函数定义在<event2/event.h>文件中。

 

八:释放Libevent全局结构

         即使你已经释放了所有Libevent分配的对象,依然会残留一些全局分配的结构。一般来说这不会有问题:一旦程序退出了,所有资源都会被清理。但是,保留这些全局结构可能会使一些调试工具认为Libevent有内存泄露。如果希望Libevent释放所有内部“库全局”数据结构的话,需要调用:

void  libevent_global_shutdown(void);

 

         注意,该函数不会释放任何Libevent返回给你的结构体。你若希望退出之前释放所有内存,则必须亲自释放所有的events, event_bases,bufferevents等。

         调用libevent_global_shutdown()会使其他的Libevent函数变得不可预知。所以该函数应该作为最后一个调用的Libevent函数。不过该函数具有幂等性,可以多次调用。(幂等性是说一个操作不管是执行一次还是多次,产生的副作用是一样的。幂等性是系统的接口对外一种承诺(而不是实现), 承诺只要调用接口成功, 外部多次调用对系统的影响是一致的。)

         该函数在<event2/event.h>中声明。

 

 

原文:http://www.wangafu.net/~nickm/libevent-book/Ref1_libsetup.html

posted @ 2015-01-08 17:17  gqtc  阅读(379)  评论(0编辑  收藏  举报