libevent學習三
來源:程序員人生 發布時間:2015-03-04 07:54:17 閱讀次數:4315次
設定Libevent
Libevent有1些貫穿于全部程序的全局設定。它們影響著全部庫。你必須提早肯定這些設定,否則可能會造成不1致的狀態。
Libevent的日志消息
Libevent可以記錄內部的毛病和經過。固然編譯時增加了日志支持,它也會記錄調試信息。默許情況下,這些信息輸出到標準毛病。你可使用自己的調試方法去重寫它。
#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);
event_set_log_callback就是注冊日志輸出回調的方法。如果傳入NULL,它就會默許的輸出到標準毛病。重寫1個event_log_cb去定制自己的日志記錄。
例子:
#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函數,極可能是不安全的。比如,如果你在1個log回調中使用bufferevents去向1個socket寫入1些正告信息,那你可能會遇到很奇異的bug。這1限制可能會在將來的版本中被解決。
默許情況下,debug日志是未被啟用的,不會被發送到日志回調中去。你可以手動的去開啟它。
接口
#define EVENT_DBG_NONE 0
#define EVENT_DBG_ALL 0xffffffffu
void event_enable_debug_logging(ev_uint32_t which);
debug信息冗雜,而且在大多數情況下,沒有甚么用。EVENT_DBG_NONE默許不開啟debug日志。EVENT_DBG_ALL會開啟對debug日志的支持。
處理致命毛病
當Libevent檢測到1個致命的內部毛病(如1個被破壞的結構),它的默許行動是調用exit()或abort()去終止進程。你可以提供1個自己的處理函數,去覆蓋Libevent對這類致命毛病的處理。
接口
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
提供你自己的event_fatal_cb版本,傳到event_set_fatal_callback()里面。當有致命毛病產生時,它就會調用你提供的方法了。在你的方法里不能再把控制權交給Libevent,1旦你的方法被調用,不要在里面再調用任何Libevent方法。
內存管理
默許情況下,Libevent使用C語言庫提供的內存相干方法(malloc,realloc,free)去從堆上分配內存。當你有更高效的內存管理方案,或你想去查詢內存泄漏的時候,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));
下面有個簡單的例子,它統計被使用的內存大小。在實際環境中,你可能需要加鎖去避免1些由于多線程釀成的毛病。
#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);
}
注:
1. 替換內存管理方法會影響所有的內存分配,重分配和釋放。所以必須確保在Libevent被使用前設置你提供的方法。否則你可能會用你提供的釋放函數去釋放從C庫分配來的內存。
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. 如果使用了多線程,需要注意線程安全。
8. 如果你提供了malloc和realloc,那末你同時也需要提供對應版本的free函數。
鎖和線程
如果你有多線程的經驗,那你可能知道,同1時間多個線程訪問同1個數據多是不安全的。
在多線程環境下,Libevent的數據1般以3種方式存在。
1. 1些數據只用在單線程中:多個線程同時使用它,1定是不安全的。
2. 1些數據可以被選擇性的鎖定:在使用這些結構時,你可以告知它是不是用在多線程。
3. 1些數據總是被鎖定:運行在多線程下,它們總是安全的。
要在Libevent中取得鎖,你必須提供相干鎖方法給Libevent。并且要確保在數據被多線程使用之前就必須提供好。
如果你想使用pthread庫或Windows本地線程庫,這里提供了1些預定義的方法去讓Libevent正確安裝pthread或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。
如果你不想使用Libevent線程庫,那你需要做1些額外的工作。你需要定義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
然后你可以通過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_versionbi必須被設置成
EVTHREAD_LOCK_API_VERSION。supported_locktypes字段描寫的是鎖的類型,必須被設置為EVTHREAD_LOCKTYPE_*。alloc方法必須返回1個指定locktype的鎖。free方法必須釋放1個指定鎖的所有資源。lock方法必須實現去以指定模式取得某個鎖,成功返回0,失敗返回非0。unlock方法必須實現嘗試去解鎖,成功返回0,失敗返回非0。
公認的鎖類型:
0:常規的,不需要可重入的鎖
EVTHREAD_LOCKTYPE_RECURSIVE:如果1個線程已持有了這個鎖,再次要求不會產生死鎖。1旦持有鎖的這個線程解鎖的次數和初始加鎖的次數相同,其他線程可以取得這個鎖。
EVTHREAD_LOCKTYPE_READWRITE: 這個鎖允許多個線程同時去讀,但是只允許1個線程去寫。
公認的鎖模式:
EVTHREAD_READ:用于讀寫鎖,取得或釋放鎖用于讀
EVTHREAD_WRITE: 用于讀寫鎖,取得或釋放鎖用于寫
EVTHREAD_TRY:探查鎖是不是可以被獲得
id_fn參數是1個方法,它可以返回當前正在運行的線程的id,返回值是1個無符號長整型類型。同1個線程返回相同的數。不同的線程1定返回不同的數。
evthread_condition_callbacks結構描寫條件變量的相干回調。lock_api_version字段必須是EVTHREAD_CONDITION_API_VERSION。alloc_condition方法必須返回1個指向新條件變量的指針,它的參數是0。free_condition函數用來釋放被條件變量所持有資源。wait_condition函數有3個參數:1.被alloc_condition分配的條件變量, 2. 被evthread_lock_callbacks.alloc分配的鎖變量,3.
可選的超時時間。函數被調用的時候,它1定持有著鎖;然后釋放鎖,1直等待到接收了條件變量的信號或等待timeout時間溢出。wait_condition產生毛病返回⑴,被條件變量信號喚醒返回0,超時返回1。在它返回之前,必須確保它再次持有鎖。最后,如果broadcast為false,signal_function會喚醒1個等待在此條件變量上的線程,如果broadcast為true,喚醒所有等待在此條件變量上的線程。
調試鎖功能
Libevent提供了“調試鎖”的特性。調試鎖是用來捕獲鎖調用出現的1些特定毛病:
1. 解鎖1個當前線程未持有的鎖。
2. 重鎖1個非重入鎖。
如果產生了以上的毛病,Libevent會直接退出。
接口
void evthread_enable_lock_debugging(void);
#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()
注:這個方法
必須在鎖被創建或使用前就調用。可以在設置線程功能后就調用它。
調試事件功能
對事件的1些常規毛病,Libevent是可以檢測并且報告給你的。他們包括:
1. 使用1個未初始化的事件結構體
2. 2次初始化1個事件的結構體
追蹤事件是不是被初始化,需要額外的內存和CPU,所以僅需在調試階段的時候才需要去開啟它。
接口
void event_enable_debug_mode(void);
這個方法必須在event_base創建前就被調用。
在使用調試模式時,如果你的程序使用了大量由event_assign()[而不是event_new()]創建的event,那可能會造成內存溢出。這是由于當使用event_assign()去創建1個event時,它多是在棧上的1個對象,Libevent沒辦法辨別這個event甚么時候不再被使用。(而當使用event_new()創建event的時候,它是1個堆內存上的數據,Libevent是可以通過調用event_free()去知道這個event是無效的)。固然如果你想在調試的時候不出現內存溢出,你可以顯示的告知Libevent,這些events不再被當作是分配的。
接口
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);
}
調試的詳細情況可以通過在編譯時添加'-DUSE_DEBUG'參數來啟用。如果這個標志被啟用,那末使用Libevent程序的
服務器端會輸出1個非常冗雜的日志。其中包括:
1. event 增加
2. event 刪除
3. 平臺相干的event通知信息
這個功能不能通過API去啟用或禁用,所有只能被用在開發階段。
釋放全局Libevent數據
當你釋放了所有Libevent的數據,可1些全局的數據仍然被留下來。正常情況下是沒問題的,由于1旦程序退出,內核會自動釋放進程的所有內存。但是如果你使用1些內存泄漏的檢測工具,來檢查內存泄漏的情況時,這些全局數據會被當作未釋放的內存被檢測出來,影響你的調試。你可以通過調用以下接口:
接口:
void libevent_global_shutdown(void);
這個方法不會釋放那些通過Libevent方法返回給你的數據。你需要自己手動的去釋放它們。
這個方法應當在最后調用,就是調用它后,不要再使用任何Libevent的方法。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈