• 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 ", 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. ");

            return -1;

        } else {

            printf("Running with Libevent  version %s ", 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). ", 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

  • 相关阅读:
    String.Format使用方法
    在gfs2中关闭selinux
    开发板怎样开启telnet服务
    Google App Engine 学习和实践
    Ewebeditor最新漏洞及漏洞大全
    为HttpStatusCodeResult加入customErrors
    【android】下载文件至本应用程序的file文件夹或者sdcard
    java entry
    关闭对话框,OnClose和OnCancel
    主成分分析(PCA)
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247261.html
Copyright © 2020-2023  润新知