(个人能力有限,翻译疏漏,还望谅解,不喜勿喷)查看官方原文请点击这里
0 Preliminaries
- 瞥一眼
Libevent是编写快速便捷非阻塞IO的库,有以下几部分组成:
evutil : 通常用来提取不同平台网络实现的区别;
event and event_base : Libevent的核心模块,提供sockets可读写时刻,超时监听,检测操作系统新号等功能;
bufferevent : 更好的封装Libevent 内核,让程序可直接读写缓存,不需要实时监听 sockets来读写数据。可提供多后台运行线程,能够利用系统的快速非阻塞IO机制,例如Windows的IOCP API。
evbuffer : 该模块实现bufferevents下的缓存,提供高效的与或访问功能;
evhttp : 简单的HTTP client/server 实现;
evdns : 简单的DNS client/server 实现;
evrpc : 简单的RPC实现;
- 组成库
编译Libevent时,默认安装以下库:
libevent_core : 所有event 和 buffer核心功能,该库包含所有的event_base, evbuffer, bufferevent和功能函数;
libevent_extra:该库定义了包含HTTP, DNS 和 RPC等 特定协议功能;
libevent:历史遗留,包含了 libevent_core 和 libevent_extra的内容,一般调用用不到该部分,未来版本可能会删除;
- 头文件
现在所有Libevent头文件安装在文件夹event2下,分为三大类:
API headers:API头文件定义了 Libevent的公共接口,这些头文件都没有特殊后缀;
Compatibility headers:包含了过时函数的定义,除非使用老版本的Libevent,否则不要包含这些头文件;
Structure headers:很明显,自然是放定义结构体的头文件了;
- 如何使用老版本的Libevent
前段时间因为公司项目需要,在我们的程序中对Libevent进行了升级,在版本更替的过程中,确实出现了很多接口的改变,这确实是让人很头疼的事,接下来我们就要写一下“If you have to work with an old version of Libevent” :
从Libevent 2.0开始已将API修改的更合理、容错性更好,但是有时候因为某些原因我们需要使用老版本(低于Libevent 2.0)的API,老版本的Libevent具有更少的头文件,且不在文件夹“event2”下,如图所示:
在 Libevent 2.0以后, 老版本的头文件被封装在新版头文件里。
1 设置 Libevent库
Libevent有一些整个库共享的全局设置,在调用库的任一部分之前都需要修改这些设置,否则有可能会出现不一致的状态。
- log日志
Libevent能够打印内部错误和警告信息,默认情况下这些信息都是通过标准输出,所以我们也可以复写我们自己的log函数。接口如下:
#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的log,只需要写一个上面的函数作为参数传入event_set_log_callback(),当Libevent要打印log时会将信息穿给我们自己写的函数,可以向event_set_log_callback()传入NULL使log以默认方式输出。看下面一个例子:
#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 callback函数里面调用Libevent函数是不安全的。比如在回调函数里面使用bufferevents向网络socket写警告信息,很有可能遇到奇怪而又难以诊断的问题,这个局限性问题在未来版本的Libevent中可能会被解决。默认debug logs不可用,可手动开启:
#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是开始所有log,以后的版本可能会支持更多选项。这些功能函数声明在“event2/event.h”。
- 处理致命错误
当Libevent感知到不可恢复的内部错误时,默认执行是调用exit() 或 abort()函数退出当前运行程序,因为这些错误意味着在你写的程序中或者Libevent库中存在bug。我们还可以像下面一样重写callback函数来处理致命错误:
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
上面的函数同样声明在“event2/event.h”,Libevent 2.0.3-alpha以后才有。需要注意的是,在我们自定义的callback调用函数里,不能调用Libevent的任何函数。
- 内存管理
默认情况,Libevent使用C内存管理方法从堆中分配内存,Libevent提供替换malloc, realloc, 和free的接口,这样你就可以设置自己想要使用的内存管理策略。接口如下所示,声明在“event2/event.h”, 第一次出现在Libevent 2.0.1-alpha.
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结构体具有三种多线程工作方式:(1)本质上的单线程结构;(2)可选择加锁结构;(3)持续加锁结构;
想要实现加锁,需要告诉Libevent使用的加锁函数,如果是使用线程库或者本地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;如果想要使用别的线程库就需要做一些工作了,定义如下函数:
Locks
locking
unlocking
lock allocation
lock destruction
Conditions
condition variable creation
condition variable destruction
waiting on a condition variable
signaling/broadcasting to a condition variable
Threads
thread ID detection
然后需要调用Libevent的evthread_set_lock_callbacks和evthread_set_id_callback接口:
#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定义了加锁的callback函数和他们的功能,alloc函数返回特定类型的新锁;free函数释放特定类型锁持有的所有资源;lock函数申请对特定类型加锁,unlock函数解锁,这两个函数都是返回0值代表加锁成功,非零失败;对于上面这些函数的使用例子,可以参考文件evthread_pthread.c和evthread_win32.c。
- 查看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);
例子:Compile-time
#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
}
例子: Run-time
#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;
}
event_get_version()第一次出现在版本Libevent 1.0c,其他函数第一次出现在Libevent 2.0.1-alpha。
- 释放全局结构体
尽管已经释放了Libevent分配的所有对象,还是会有一些分配的全局结构体存在,虽然程序退出时它们也会被回收,但是对于一些调试工具而言会被误导为是Libevent库的弱链接资源,调用下面的接口函数可主动释放所有的全局结构体对象:
void libevent_global_shutdown(void);
2 event_base
在使用libevent时需要分配一个或多个event_base,每个event_base持有多个event,还能够决定哪一个event处于活动状态。为了多线程访问安全,event_base可加锁,但是同一时刻只能有一个线程在运行loop。
每个event_base都有自己的“method”或后台决定哪个event是待运行状态,“method”包括以下这些:select、poll、epoll、kqueue、devpoll、evport、win32。调用者可以通过变量关闭特定后台。例如你想关闭kqueue后台就设置环境变量EVENT_NOKQUEUE等。
- 创建默认设置的event_base
event_base_new()可以分配并返回一个默认设置的event_base,出错则返回NULL,对于大多数程序来说,默认设置生成的event_base的足够使用了。 event_base_new()函数声明在文件“event2/event.h”,在版本1.4.3第一次开始使用。
struct event_base *event_base_new(void);
- 创建复杂的event_base
通过event_config可自定义event_base。event_config可存储关于一个event_base的偏好设置信息,可通过向函数 event_base_new_with_config()传入event_config来获取想要的event_base。
struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);
通过event_config_new()生成event_config,然后调用其他函数对其设置个人需求,最后调用event_base_new_with_config()生成event_base,使用完以后通过调用 event_config_free()释放event_config。
int event_config_avoid_method(struct event_config *cfg, const char *method);
enum event_method_feature {
EV_FEATURE_ET = 0x01,
EV_FEATURE_O1 = 0x02,
EV_FEATURE_FDS = 0x04,
};
int event_config_require_features(struct event_config *cfg,
enum event_method_feature feature);
enum event_base_config_flag {
EVENT_BASE_FLAG_NOLOCK = 0x01,
EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,
enum event_base_config_flag flag);
调用event_config_avoid_method()函数通知Libevent 通过名字消除特定功能的后台;调用event_config_require_feature()通知Libevent不使用不具有该属性的的后台;调用event_config_set_flag()通知Libevent构造event base时设置一个或多个运行时标识。
常用特征值有以下几种:
EV_FEATURE_ET :后台方法需支持 edge-triggered IO
EV_FEATURE_O1 :要求后台方法增删单一event或者将某event激活,是一个O(1)操作。
EV_FEATURE_FDS:要求后台方法不仅支持sockets,而且可以支持任意文件描述符类型。
event_config_set_flag()函数常用选项值有以下几种:
EVENT_BASE_FLAG_NOLOCK :不为event_base分配锁,设置此选项可以节省一点时间锁定和释放的event_base ,但是也会使多线程访问时变得不安全。
EVENT_BASE_FLAG_IGNORE_ENV:当选择使用哪个后台方法时不会检查EVENT_*环境变量,若是打算使用该标识请三思而后行:它会增加使用者调试Libevent与程序之间相互作用的难度。
EVENT_BASE_FLAG_STARTUP_IOCP:只适用于windows,
EVENT_BASE_FLAG_NO_CACHE_TIME:每次超时回调才会检查,而不是event loop一直实时检查。
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST :
EVENT_BASE_FLAG_PRECISE_TIMER :
注意:因为操作系统的区别(例如windows,linux)如果调用event_config配置了Libevent在该系统下无法提供的设置,event_base_new_with_config()将返回NULL
3 Running an event loop
(待更新)