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