zoukankan      html  css  js  c++  java
  • 设置Libevent库



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

    该章节的宏和函数都在中定义,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中引进。

  • 相关阅读:
    Java代码输出到txt文件(申请专利贴源码的必备利器)
    Vmware10组建局域网
    Ubuntu14.04更换阿里云源
    Ubuntu16.04如何彻底删除Apache2
    HustOJ平台搭建
    Centos 7 联想Y430P无线网卡驱动安装 过程参考
    Windows远程CentOS桌面
    centos 7 查看系统/硬件信息及运维常用命令+联想Y430P无线网卡驱动安装
    zookeeper工作原理、安装配置、工具命令简介
    centos 7 安装五笔输入法
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318287.html
Copyright © 2011-2022 走看看