接口
#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簽名的定制函數(shù),將其作為參數(shù)傳遞給event_set_log_callback()。隨后libevent在日志信息的時候,將會把信息傳遞給你提供的函數(shù)。再次調(diào)用event_set_log_callback(),傳遞參數(shù)NULL,就可以恢復(fù)默認(rèn)行為。
示例
#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回調(diào)函數(shù)中調(diào)用libevent函數(shù)是不安全的。比如說,如果試圖編寫一個使用bufferevent將警告信息發(fā)送給某個套接字的日志回調(diào)函數(shù),可能會遇到奇怪而難以診斷的bug。未來版本libevent的某些函數(shù)可能會移除這個限制。
這個函數(shù)在<event2/event.h>中聲明,在libevent 1.0c版本中首次出現(xiàn)。
2 處理致命錯誤
libevent在檢測到不可恢復(fù)的內(nèi)部錯誤時的默認(rèn)行為是調(diào)用exit()或者abort(),退出正在運行的進(jìn)程。這類錯誤通常意味著某處有bug:要么在你的代碼中,要么在libevent中。
如果希望更優(yōu)雅地處理致命錯誤,可以為libevent提供在退出時應(yīng)該調(diào)用的函數(shù),覆蓋默認(rèn)行為。
接口
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
要使用這些函數(shù),首先定義libevent在遇到致命錯誤時應(yīng)該調(diào)用的函數(shù),將其傳遞給event_set_fatal_callback()。隨后libevent在遇到致命錯誤時將調(diào)用你提供的函數(shù)。
你的函數(shù)不應(yīng)該將控制返回到libevent:這樣做可能導(dǎo)致不確定的行為。為了避免崩潰,libevent還是會退出。你的函數(shù)被不應(yīng)該調(diào)用其它libevent函數(shù)。
這些函數(shù)聲明在<event2/event.h>中,在libevent 2.0.3-alpha版本中首次出現(xiàn)。
3 內(nèi)存管理
默認(rèn)情況下,libevent使用C庫的內(nèi)存管理函數(shù)在堆上分配內(nèi)存。通過提供malloc、realloc和free的替代函數(shù),可以讓libevent使用其他的內(nèi)存管理器。希望libevent使用一個更高效的分配器時;或者希望libevent使用一個工具分配器,以便檢查內(nèi)存泄漏時,可能需要這樣做。
接口
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分配器函數(shù)的示例,它可以計算已經(jīng)分配的字節(jié)數(shù)。實際應(yīng)用中可能需要添加鎖,以避免運行在多個線程中時發(fā)生錯誤。
示例
#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);
}
注意
v 替換內(nèi)存管理函數(shù)影響libevent隨后的所有分配、調(diào)整大小和釋放內(nèi)存操作。所以,必須保證在調(diào)用任何其他libevent函數(shù)之前進(jìn)行替換。否則,libevent可能用你的free函數(shù)釋放用C庫的malloc分配的內(nèi)存。
v 你的malloc和realloc函數(shù)返回的內(nèi)存塊應(yīng)該具有和C庫返回的內(nèi)存塊一樣的地址對齊。
v 你的realloc函數(shù)應(yīng)該正確處理realloc(NULL,sz)(也就是當(dāng)作malloc(sz)處理)
v 你的realloc函數(shù)應(yīng)該正確處理realloc(ptr,0)(也就是當(dāng)作free(ptr)處理)
v 你的free函數(shù)不必處理free(NULL)
v 你的malloc函數(shù)不必處理malloc(0)
v 如果在多個線程中使用libevent,替代的內(nèi)存管理函數(shù)需要是線程安全的。
v libevent將使用這些函數(shù)分配返回給你的內(nèi)存。所以,如果要釋放由libevent函數(shù)分配和返回的內(nèi)存,而你已經(jīng)替換malloc和realloc函數(shù),那么應(yīng)該使用替代的free函數(shù)。
event_set_mem_functions函數(shù)聲明在<event2/event.h>中,在libevent 2.0.1-alpha版本中首次出現(xiàn)。
可以在禁止event_set_mem_functions函數(shù)的配置下編譯libevent。這時候使用event_set_mem_functions將不會編譯或者鏈接。在2.0.2-alpha及以后版本中,可以通過檢查是否定義了EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED宏來確定event_set_mem_functions函數(shù)是否存在。
4 鎖和線程
編寫多線程程序的時候,在多個線程中同時訪問同樣的數(shù)據(jù)并不總是安全的。
libevent的結(jié)構(gòu)體在多線程下通常有三種工作方式:
v 某些結(jié)構(gòu)體內(nèi)在地是單線程的:同時在多個線程中使用它們總是不安全的。
v 某些結(jié)構(gòu)體具有可選的鎖:可以告知libevent是否需要在多個線程中使用每個對象。
v 某些結(jié)構(gòu)體總是鎖定的:如果libevent在支持鎖的配置下運行,在多個線程中使用它們總是安全的。
為獲取鎖,在調(diào)用分配需要在多個線程間共享的結(jié)構(gòu)體的libevent函數(shù)之前,必須告知libevent使用哪個鎖函數(shù)。
如果使用pthreads庫,或者使用Windows本地線程代碼,那么你是幸運的:已經(jīng)有設(shè)置libevent使用正確的pthreads或者Windows函數(shù)的預(yù)定義函數(shù)。
接口
#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
這些函數(shù)在成功時都返回0,失敗時返回-1。
如果使用不同的線程庫,則需要一些額外的工作,必須使用你的線程庫來定義函數(shù)去實現(xiàn):
v 鎖
v 鎖定
v 解鎖
v 分配鎖
v 析構(gòu)鎖
v 條件變量
v 創(chuàng)建條件變量
v 析構(gòu)條件變量
v 等待條件變量
v 觸發(fā)/廣播某條件變量
v 線程
v 線程ID檢測
使用evthread_set_lock_callbacks和evthread_set_id_callback接口告知libevent這些函數(shù)。
接口
#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結(jié)構(gòu)體描述的鎖回調(diào)函數(shù)及其能力。對于上述版本,lock_api_version字段必須設(shè)置為EVTHREAD_LOCK_API_VERSION。必須設(shè)置supported_locktypes字段為EVTHREAD_LOCKTYPE_*常量的組合以描述支持的鎖類型(在2.0.4-alpha版本中,EVTHREAD_LOCK_RECURSIVE是必須的,EVTHREAD_LOCK_READWRITE則沒有使用)。alloc函數(shù)必須返回指定類型的新鎖;free函數(shù)必須釋放指定類型鎖持有的所有資源;lock函數(shù)必須試圖以指定模式請求鎖定,如果成功則返回0,失敗則返回非零;unlock函數(shù)必須試圖解鎖,成功則返回0,否則返回非零。
可識別的鎖類型有:
v 0:通常的,不必遞歸的鎖。
v EVTHREAD_LOCKTYPE_RECURSIVE:不會阻塞已經(jīng)持有它的線程的鎖。一旦持有它的線程進(jìn)行原來鎖定次數(shù)的解鎖,其他線程立刻就可以請求它了。
v EVTHREAD_LOCKTYPE_READWRITE:可以讓多個線程同時因為讀而持有它,但是任何時刻只有一個線程因為寫而持有它。寫操作排斥所有讀操作。
可識別的鎖模式有:
v EVTHREAD_READ:僅用于讀寫鎖:為讀操作請求或者釋放鎖
v EVTHREAD_WRITE:僅用于讀寫鎖:為寫操作請求或者釋放鎖
v EVTHREAD_TRY:僅用于鎖定:僅在可以立刻鎖定的時候才請求鎖定
id_fn參數(shù)必須是一個函數(shù),它返回一個無符號長整數(shù),標(biāo)識調(diào)用此函數(shù)的線程。對于相同線程,這個函數(shù)應(yīng)該總是返回同樣的值;而對于同時調(diào)用該函數(shù)的不同線程,必須返回不同的值。
evthread_condition_callbacks結(jié)構(gòu)體描述了與條件變量相關(guān)的回調(diào)函數(shù)。對于上述版本,condition_api_version字段必須設(shè)置為EVTHREAD_CONDITION_API_VERSION。alloc_condition函數(shù)必須返回到新條件變量的指針。它接受0作為其參數(shù)。free_condition函數(shù)必須釋放條件變量持有的存儲器和資源。wait_condition函數(shù)要求三個參數(shù):一個由alloc_condition分配的條件變量,一個由你提供的evthread_lock_callbacks.alloc函數(shù)分配的鎖,以及一個可選的超時值。調(diào)用本函數(shù)時,必須已經(jīng)持有參數(shù)指定的鎖;本函數(shù)應(yīng)該釋放指定的鎖,等待條件變量成為授信狀態(tài),或者直到指定的超時時間已經(jīng)流逝(可選)。wait_condition應(yīng)該在錯誤時返回-1,條件變量授信時返回0,超時時返回1。返回之前,函數(shù)應(yīng)該確定其再次持有鎖。最后,signal_condition函數(shù)應(yīng)該喚醒等待該條件變量的某個線程(broadcast參數(shù)為false時),或者喚醒等待條件變量的所有線程(broadcast參數(shù)為true時)。只有在持有與條件變量相關(guān)的鎖的時候,才能夠進(jìn)行這些操作。
關(guān)于條件變量的更多信息,請查看pthreads的pthread_cond_*函數(shù)文檔,或者Windows的CONDITION_VARIABLE(Windows Vista新引入的)函數(shù)文檔。
示例
關(guān)于使用這些函數(shù)的示例,請查看Libevent源代碼發(fā)布版本中的evthread_pthread.c和evthread_win32.c文件。
這些函數(shù)在<event2/thread.h>中聲明,其中大多數(shù)在2.0.4-alpha版本中首次出現(xiàn)。2.0.1-alpha到2.0.3-alpha使用較老版本的鎖函數(shù)。event_use_pthreads函數(shù)要求程序鏈接到event_pthreads庫。
條件變量函數(shù)是2.0.7-rc版本新引入的,用于解決某些棘手的死鎖問題。
可以創(chuàng)建禁止鎖支持的libevent。這時候已創(chuàng)建的使用上述線程相關(guān)函數(shù)的程序?qū)⒉荒苓\行。
5 調(diào)試鎖的使用
為幫助調(diào)試鎖的使用,libevent有一個可選的“鎖調(diào)試”特征。這個特征包裝了鎖調(diào)用,以便捕獲典型的鎖錯誤,包括:
v 解鎖并沒有持有的鎖
v 重新鎖定一個非遞歸鎖
如果發(fā)生這些錯誤中的某一個,libevent將給出斷言失敗并且退出。
接口
void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
注意
必須在創(chuàng)建或者使用任何鎖之前調(diào)用這個函數(shù)。為安全起見,請在設(shè)置完線程函數(shù)后立即調(diào)用這個函數(shù)。
這個函數(shù)是在2.0.4-alpha版本新引入的。
6 調(diào)試事件的使用
libevent可以檢測使用事件時的一些常見錯誤并且進(jìn)行報告。這些錯誤包括:
v 將未初始化的event結(jié)構(gòu)體當(dāng)作已經(jīng)初始化的
v 試圖重新初始化未決的event結(jié)構(gòu)體
跟蹤哪些事件已經(jīng)初始化需要使用額外的內(nèi)存和處理器時間,所以只應(yīng)該在真正調(diào)試程序的時候才啟用調(diào)試模式。
接口
void event_enable_debug_mode(void);
必須在創(chuàng)建任何event_base之前調(diào)用這個函數(shù)。
如果在調(diào)試模式下使用大量由event_assign(而不是event_new)創(chuàng)建的事件,程序可能會耗盡內(nèi)存,這是因為沒有方式可以告知libevent由event_assign創(chuàng)建的事件不會再被使用了(可以調(diào)用event_free告知由event_new創(chuàng)建的事件已經(jīng)無效了)。如果想在調(diào)試時避免耗盡內(nèi)存,可以顯式告知libevent這些事件不再被當(dāng)作已分配的了:
接口
void event_debug_unassign(struct event *ev);
沒有啟用調(diào)試的時候調(diào)用event_debug_unassign沒有效果。
這些調(diào)試函數(shù)在libevent 2.0.4-alpha版本中加入。
7 檢測libevent的版本
新版本的libevent會添加特征,移除bug。有時候需要檢測libevent的版本,以便:
v 檢測已安裝的libevent版本是否可用于創(chuàng)建你的程序
v 為調(diào)試顯示libevent的版本
v 檢測libevent的版本,以便向用戶警告bug,或者提示要做的工作
接口
#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版本;函數(shù)返回運行時的libevent版本。注意:如果動態(tài)鏈接到libevent,這兩個版本可能不同。
可以獲取兩種格式的libevent版本:用于顯示給用戶的字符串版本,或者用于數(shù)值比較的4字節(jié)整數(shù)版本。整數(shù)格式使用高字節(jié)表示主版本,低字節(jié)表示副版本,第三字節(jié)表示修正版本,最低字節(jié)表示發(fā)布狀態(tài):0表示發(fā)布,非零表示某特定發(fā)布版本的后續(xù)開發(fā)序列。
所以,libevent 2.0.1-alpha發(fā)布版本的版本號是[02 00 01 00],或者說0x02000100。2.0.1-alpha和2.0.2-alpha之間的開發(fā)版本可能是[02 00 01 08],或者說0x02000108。