LibEvent中文帮助文档--第5章【设置LibEvent库】



LiEvent中文帮助文档--第5章【设置LibEvent库】

返回主目录

Libevent

快速可移植非阻塞式网络编程

 

 

修订历史

版本

日期

作者

备注

V1.0

2016-11-15

周勇

Libevent编程中文帮助文档

 

文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

此外,本文档的源代码示例也是基于BSD"3条款""修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

英文:http://libevent.org/

中文:http://blog.csdn.net/zhouyongku/article/details/53431750

请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

<<上一章>>

5.设置LibEvent

LibEvent在进程中有一些影响整个库的全局设置.在你调用LibEvent库中任何一个部分之前都需要进行设置,否则libEvent将会进入不一致的状态.

 

5.1LibEvent日志消息

LibEvent能记录内部的错误和警告日志,如果编译进日志支持功能,也会记录调试信息.默认情况下这些消息都是输出到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 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);

为了覆盖LibEvent的日志行为,你需要自己编写满足event_log_cb格式的函数,然后将函数作为参数传入event_set_log_callback().无论什么时候只要LibEvent需要写一个日志,都会进入到你提供的日志函数.你需要让LibEvent日志回到默认功能的时候只需要再次调用event_set_log_callback()并且传一个NULL即可.

 

例子

#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去发送告警信息给一个网络socket的日志回调函数,你就可能会遇到非常奇怪和难以诊断的错误.在将来的版本中将会为一些函数移除这些限制.

 

通常调试日志都是禁用的,也都不会发送给日志回调函数,但是如果libEvent是编译成支持打开调试日志,你就可以手动打开调试日志.

 

接口

#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开启所有可支持的调试日志,未来会有更多更细的选项支持.

 

这些函数定义在<event2/event.h>,首先出现是在LibEvent1.0c版本,event_enable_debug_logging()是在LibEvent2.1.1-alpha版本.

 

LibEvent2.0.19之前兼容性需要注意的是EVENT_LOG_*宏在此前有下面的记录:_EVENT_LOG_DEBUG, _EVENT_LOG_MSG, _EVENT_LOG_WARN, and _EVENT_LOG_ERR.这些老的命名已经废弃不用,只会仅仅用于兼容LibEvent2.18及之前的版本,这些在将来的版本都会全部移除掉.

 

5.2处理致命错误

LibEvent检测到一个不可恢复的致命错误(比如数据结构损坏),它的默认行为是调用exit()abort()来退出当前运行的进程.这意味着有一个错误,要么在你的代码中,要么在LibEvent.

 

如果你想让你的程序更从容地应对致命错误,你可以为LibEvent提供退出时候应该调用的函数,覆盖默认行为.

 

接口

typedef void ( * event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);

要使用这些函数,你首先需要定义一个当LibEvent遇到错误时候需要调用的函数,并且将它传到event_set_fata_callback()函数.之后如果LibEvent遇到致命错误,它就会调用你提供的函数.

 

你的函数不可以控制返回到LibEvent,因为这样做可能会导致未定义行为发生,为了避免崩溃,LibEvent还是会退出.一旦你的程序被调用了,在你的函数中就不应该再调用别的LibEvent的函数.

 

这些函数被定义在<event2/event.h>,第一次出现在LibEvent2.0.3-alpha版本中.

 

5.3内存管理

默认情况下,LibEvent使用c语言库中提供的内存管理函数在堆上分配内存,你也可以替换mallocreallocfree,LibEvent使用别的内存管理.你想用一个更有效的内存分配器,或者用一个内存分配工具来检查内存泄露你都可能会这样做.

 

接口

void event_set_mem_functions( void* ( * malloc_fn)(size_t sz),
void* ( * realloc_fn)(void * ptr, size_t sz),
void  ( * free_fn)(void* ptr));

例子

#include <event2/event.h>
#include <sys/types.h>
#include <stdlib.h>
/* This union’s purpose is to be 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 a double.*/
#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 not standard.*/
#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);
}

注意:更换内存管理函数将会影响LibEvent后续所有调用allocateresizefree内存的函数.因此你需要确保在LibEvent调用其它函数之前替换掉这些函数.否则LibEvent将会调用你提供的free函数来释放从C语言库版本的malloc分配的内存.

  • 你的malloc和realloc函数需要返回和C语言库相同的内存对齐.

  • 你的realloc函数需要正确处理realloc(NULL,sz),也就是说当做(malloc(sz)处理).

  • 你的realloc函数需要正确处理realloc(ptr,0),也就是说当做free(ptr)处理.

  • 你的free函数不必去处理free(NULL).

  • 你的malloc函数不必去处理malloc(0).

  • 如果你不止一个线程使用LibEvent,那么你提供的的内存管理替代函数必须是线程安全的.

  • LibEvent会使用这些函数分配返回给你的内存.

     

    如果你已经替换了mallocrealloc函数并且想释放LibEvent分配的内存,那么就需要使用你替换的free函数来释放.

    event_set_mem_functions()函数在<event2/event.h>中申明,首次出现是在LibEvent2.0.1alpha版本.

    LibEvent可以被编译为event_set_mem_functions()禁用,如果这样,event_set_mem_functions()函数不会被编译和链接到程序.LibEvent2.0.2-alpha版本之后,你可以检查是否定义了EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED宏来检测event_set_mem_functions()函数是否存在.

5.4线程和锁

如果你写过多线程程序,你会知道在多线程中同一时间访问同一个数据通常是不安全的.

LibEvent的结构体在多线程一般有三种工作方法:

  • 某些结构体在单线程内部是安全的,但在多线程中同时访问不再是安全的.

  • 某些结构体是有可选的锁的,你需要告知LibEvent是否需要在多线程中使用每个对象.

某些结构体是一直锁住的,如果LibEvent在运行中支持锁,那么在多线程中用这些结构体是安全的.

 

为了LibEvent上锁,你需要告知LibEvent使用的是哪个锁函数,在调用任何LibEvent函数分配一个线程间共享的结构之前你就需要这样做.

 

如果你用的是pthreads库或者是windows的线程代码,那么你很幸运,那些预定义函数将会帮你把LibEvent设置为正确支持pthreadswindows的函数.

 接口

#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, 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);
	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_function返回一个只想新的条件变量的指针,0最为其参数.函数free释放条件变量持有的资源和存储.wait_function函数有三个参数:alloc_condition分配的条件、你提供的evthread_lock_callabcks.alloc函数分配的锁、可选的超时条件.无论什么时候函数调用锁都会被持有,函数必须释放锁,等到变量有信号,或者直到超时的时间已经流逝.wait_condition应该在错误时返回-1,条件变量授信时返回0,超时时返回1.返回之前,函数应该确定其再次持有锁.最后,signal_condition函数应该唤醒等待该条件变量的某个线程( broadcast参数为false)或者唤醒等待条件变量的所有线程(broadcast参数为true).只有在持有与条件变量相关的锁的时候,才能够进行这些操作.关于条件变量的更多信息,请查看 pthreadspthread_cond_*函数文档,或者 WindowsCONDITION_VARIABLE( Windows Vista新引入的)函数文档.


示例
关于使用这些函数的示例,请查看Libevent源代码发布版本中的evthread_pthread.cevthread_win32.c文件.这些函数在<event2/thread.h>中声明,其中大多数在2.0.4-alpha版本中首次出现.2.0.1-alpha2.0.3-alpha使用较老版本的锁函数.event_use_pthreads函数要求程序链接到event_pthreads.条件变量函数是2.0.7-rc版本新引入的,用于解决某些棘手的死锁问题.可以创建禁止锁支持的 libevent.这时候已创建的使用上述线程相关函数的程序将不能运行.

5.5 调试锁的使用

为了辅助调试锁使用,LibEvent有一个"锁调试"特殊选项,它包裹锁了调用,以便获取到典型的锁错误,包括:

  • 解锁但实际上并不能持有的锁.

  • 重复锁定一个非递归的锁.

如果其中这些锁发生了错误,LibEvent将会伴随断言错误退出.

接口

void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()

注意:这个函数必须在任何锁创建和使用之前调用,为了保证安全,在设置你的线程函数之后就调用.

这个函数首次以拼写错误的名称"evthread_enable_lock_debuging()."出现在LibEvent2.0.4-alpha版本,2.1.2-alpha版本之后拼写固定为"evthread_enable_lock_debugging()",现在这两个名称都是支持的.


 

5.6调试事件使用

有一些常用的错误LibEvent会检测到并且报告给你,他们是:

  • 把一个未初始化的event结构体当做已经初始化.

  • 试图重新初始化一个未完成的event结构.

跟踪哪些event初始化了LibEvent需要额外的内存和CPU,所以在实际调试你的程序的时候你应该启用调试模式.

 

接口

void event_enable_debug_mode(void);

该函数必须在所有event_base创建之前调用.

当启用调试模式,如果你用event_assign()(不是event_new()函数 )创建了大量的events,你的程序可能会运行内存不足,这是因为LibEvent无法获知event_assign创建的event是否不再使用(可以调用event_free()通知event_new()创建的对象已经失效).如果你想避免运行内存不足的情况发生,你要明确地告诉LibEvent这些event都不再被分配.

 

接口

void event_debug_unassign(struct event* 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);
}

详细的事件调试功能的启用只能在编译时使用CFLAGS环境变量"-DUSE_DEBUG".开启这个标志后任何编译的LibEvent程序都记录底层和后台的详细日志.这些日志包含但不限于下面所示:

  • 事件添加

  • 事件删除

  • 平台特定的事件通知信息

这种特性不能通过API调用来关闭和开启,所有必须在开发人员创建程序的时候使用.

这些调试功能加进了LibEvent2.0.4-alpha版本.

5.7检查LibEvent的版本信息

新版本的LibEvent会添加特性,移除错误,有时候你想要查询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,这两个版本可能不同.

 

以获取两种格式的libevent版本:用于显示给用户的字符串版本或者用于数值比较的4字节整数版本.整数格式使用高字节表示主版本,低字节表示副版本,第三字节表示修正版本,最低字节表示发布状态:0表示发布,非零表示某特定发布版本的后续开发序列.


所以,libevent 2.0.1-alpha发布版本的版本号是[02 00 01 00],或者说0x02000100.

2.0.1-alpha2.0.2-alpha之间的开发版本可能是[02 00 01 08],或者说0x02000108.

 

示例:编译时检测

#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)
{
    /* Let’s suppose that Libevent 6.0.5 introduces a make-me-a sandwich function.*/
#if LIBEVENT_VERSION_NUMBER >= 0x06000500
	evutil_make_me_a_sandwich();
	return 0;
#else
	return -1;
#endif
}

示例:运行时检测

#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 is the 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 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;
}

本章节定义的宏和函数定义在<event2/event.h>,event_get_version()函数首先出现是在LibEvent1.0c版本,其余出现在LibEvent2.0.1-alpha版本.

 

5.8释放LibEvent全局结构体

即便你释放了所有LibEvent分配的对象,任然会有一些全局分配的结构体保留,通常这都不是什么问题,当进程退出的时候他们都将被清理干净,但是这些残存的结构体会导致某些调试工具认为LibEvent有资源泄露.如果你确实需要确保LibEvent释放了所有库内部的全局数据结构,那么你可以调用:

接口

void libevent_global_shutdown(void);

这个函数不会释放任何libEvent函数返回给你的结构体,如果你想在退出的时候释放所有,那么你需要自己释放所有的eventsevent_basesbufferevents.

 

调用libevent_global_shutdown()函数将会使得别的LibEvent的函数产生不可预知的行为.除了程序调用了最后一个LibEvent的函数否则不要调用它.

 

这个函数声明在<event2/event.h>,LibEvent2.1.1-alpha版本中出现.


<<下一章>>


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值