Libevent:2设置

摘要:
Libevent有一些整个进程共享的全局设置。因此必须在调用Libevent其他函数之前进行设置,否则,LIbevent就会陷入不一致的状态。未来Libevent版本中,针对某些函数有可能会去除该限制。如果在构建Libevent时支持的话,可以手动启用。如果希望应用进程更加优雅的处理致命错误的话,可以改变Libevent的这种默认行为。通过提供另一个函数以供Libevent调用,替代退出这种默认行为。

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)(interr);

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_callbacksevthread_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

免责声明:文章转载自《Libevent:2设置》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇深入研究 .NET 5 的开放式遥测Crontab 错误分析及不执行原因下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

winform进程、线程

随笔- 86 文章- 0 评论- 0 winform进程、线程 进程: 一般来说,一个程序就是一个进程,不过也有一个程序需要多个进程支持的情况。 进程所使用的类:Process 所需命名空间:System.Diagnostics; 可以通过进行来开启计算机上现有的程序: 1、使用静态方法Start();但必须要知道进程名 2、也可以实例化对象,来调用S...

Linux平台Cpu使用率的计算

proc文件系统 /proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取/proc目录中的文件时,proc文件系统是动态从系统内核读出所需信息并提交的。 /pr...

Spring Boot 异步请求和异步调用,一文搞定

一、Spring Boot中异步请求的使用 1、异步请求与同步请求     特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。 一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通...

调试JDK源码时可编辑操作的实现

目录 一、解压源码压缩包 二、创建Java项目 三、复制源码文件到文件夹 四、Platform Settings中新增一个SDK 五、修改新建SDK的 Sourcepath 配置 六、修改 Project 以及 Modules 的 SDK 七、项目结构图 八、测试调试过程中对源码进入注释 以下操作在以下环境中测试通过:idea 2019.3,jd...

mysql主从复制延迟问题的相关知识与解决方案

一、如何监控发生了主从延迟? 在从库机器上,执行show slave status,查看Seconds_Behind_Master值,代表主从同步从库落后主库的时间,单位为秒,若同从同步无延迟,这个值为0。 Mysql主从延迟一个重要的原因之一是:mysql是以单线程串行执行。 主从复制数据时,在从服务器上的mysql,是一个线程在同步数据。 串行的方式,...

C#中的Thread.IsBackground的琢磨

今天在执行一段前后台线程的时候,发现了我把线程全部都设置成了后台线程IsBackground = true,但是还是会执行后台线程,一直执行完后台线程。 代码如下: usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Linq.Expressions; us...