初入libevent的人,極可能是第一次接觸異步編程。Libevent的編程思想,建議仍是多看前人的程序,或者是看libevent自己的文檔學習。
或者是介紹另一個庫,那就是libuv,它是libev某種意義上的的替代品(而libev又能夠算是libevent的某種替代品笑)。libuv的文檔我記得也有對異步編程的介紹。好了,這不是本文的內容。html
本文地址:https://segmentfault.com/a/1190000005359466算法
Programming with Libevent
翻譯:Libevent參考手冊第一章:設置libevent編程
在文檔序章中舉了一個例子,介紹了異步編程。這裏面列舉了幾個典型的函數,以下:segmentfault
頭文件#include <event2/event.h>
後端
結構體struct event
數組
函數event_new(), event_free(), event_base_new(), event_add(), event_base_dispatch()
緩存
Libevent的基本思路是使用select()
。可是使用select不少的話,每每系統開銷很大,因此各個UNIX和類UNIX系統是使用了select的替代。可是不一樣的系統使用的替代各不相同,這致使程序移植起來很麻煩。
Linux:epoll()
BSD:kqueue()
Solaris:/dev/poll
, evports
安全
Libevent使用BSD License而不是GPL,這使得它能夠很方便地用在一個閉源的項目裏面網絡
evutil
:不一樣平臺的抽象層,主要是網絡接口部分event
, event_base
:libevent的核心APIbufferevent
:緩存化的read/write接口,而且能夠與SSL互相封裝。另外還有幾個我就不列出了,我的不多用異步
libevent_core
:Libevent的核心API和功能,包含了上述主要模塊libevent_extra
:Libevent提供的額外功能包,這些目前還用不上libevent
:包含上述兩個,可是建議之後不要使用之因此還有event2
文件夾,是由於這個文件夾下是新的(也就是目前使用的)libevent庫
每個event_base
持有和管理多個event
,而且判斷哪些event
是被激活了的。可是這發生在一個單一的線程內。若是你想喲啊在多個線程內並行處理event,則須要在每一個線程中各建立一個event_base。
struct event_base *event_base_new(void);
這個函數alloc並返回一個帶默認配置的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);
這幾個函數的基本功能就是:得到一個config,配置好後做爲event_base建立的參數。用完後記得將config給free掉。
而配置config則須要用到要如下兩個函數:
int event_config_require_features(struct event_config *cfg, enum event_method_feature feature);
這裏的枚舉值有如下幾個:EV_FEATURE_ET
:須要邊沿觸發的I/OEV_FEATURE_OI
:在add和delete event的時候,須要後端(backend)方法EV_FEATURE_FDS
:須要可以支持任意文件描述符的後端方法
上述的值都是mask,能夠位與(|)。
int event_config_set_flag(struct event_config *cfg, enum event_base_config_flag flag);
這裏的枚舉值有如下幾個:EVENT_BASE_FLAG_NOLOCK
:event_base不使用lock初始化EVENT_BASE_FLAG_IGNORE_ENV
:挑選backend方法時,不檢查EVENT_xxx標誌。這個功能慎用EVENT_BASE_FLAG_STARTUP_IOCP
:僅用於Windows。起碼我不關心EVENT_BASE_FLAG_NO_CACHE_TIME
:不檢查timeout,代之覺得每一個event loop都準備調用timeout方法。這會致使CPU使用率偏高EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
:告訴libevent若是使用epoll的話,可使用基於「changlist」的backend。EVENT_BASE_FLAG_PRECISE_TIMER
:使用更加精確的定時機制
以上兩個函數成功時返回0,失敗時返回-1;
int event_config_set_max_dispatch_interval ( struct event_config *cfg, const struct timerval *mac_interval, int max_callbacks, int min_priority);
在檢查高優先級的event以前,經過限制低優先級調用來防止優先級反轉(priority inversion)。而另外一個限制條件是max_interval
參數,這是表示高優先級事件被調用的最大延時。
成功時返回0,失敗時返回-1。默認狀況下,`event_base`被設置成默認優先級
const char **event_get_supported_methods(void);
返回一個char*數組,說明了libevent支持的全部backend方法。數組以NULL
結尾。
const char *event_base_get_method (const struct event_base *base); enum event_method_feature event_base_get_features (const struct event_base *base);
int event_base_priority_init (structr event_base *base, int n_priorities);
第二個參數表示可支持的優先級,至少爲1。0是最優先。這樣,event_base中的優先級即爲0 ~ nPriorities-1
。
最大支持的優先級數爲EVENT_MAX_PRIORITIES
。
int event_base_get_npriorities(struct event_base *base);
上一個函數的讀版本
當event_base和其中的event都配置好了以後,就能夠啓用libevent了。Event的配置在後文說,這裏先講event loop的運行。
int event_base_loop (struct event_base *base, int flags); int event_base_dispatch (struct event_base *base);
函數的行爲是:運行event_base,直到沒有event被註冊在event_base中位置。這是默認的行爲,也就是說,當沒有pending
或active
事件時,函數返回。
參數flags
能夠用來修改默認行爲,有如下掩碼值:
EVLOOP_ONCE
:等待並執行active事件,循環執行到沒有事件可執行EVLOOP_NONBLOCK
:循環不等待事件觸發,而是即檢查即回調EVLOOP_NO_EXIT
:沒有事件仍不退出,而是由其餘函數觸發退出第二個函數的做用是相同的,等效於使用默認的flags。
int event_base_loopexit (struct event_base *base, const struct timeval *tv); int event_base_loopbreak (struct event_base *base);
第一個函數要求event_base在指定時間後當即中止,若是tv爲NULL,則當即中止。但這個函數實際上會使得event_base在執行徹底部的callback以後才返回。
第二個函數的不一樣之處是使event_base在執行完當前的callback以後,無視其餘active事件而當即中止。但須要注意的是,若是當前沒有callback,這會致使event_base等到執行完下一個callback以後才退出。
int event_base_got_exit (struct event_base *base); int event_base_got_break (struct event_base *base);
用來判斷是否得到了exit或者是break請求。注意返回值實際上應該是BOOL而不是int
int event_base_gettimeofday_cached (struct event_base *base, struct timeval *tv); int event_base_update_cache_time (struct event_base *base);
得到event_loop時間。第一個函數得到event_loop最近的時間,比較快,可是不精確。第二個函數能夠強制event_loop當即同步更新時間。
void event_base_dump_events (struct event_base *base, FILE *f);
實際上我以爲這個函數可以保存的信息仍是不夠多。另外,你能夠將/dev/stdout
句柄打開以後傳入也是能夠的
typedef int (*event_base_foreach_Event_cb) (const struct event_base *, const struct event *, void *); int event_base_foreach_event (struct event_base *base, event_base_foreach_Event_cb fn, void *arg);
這個函數比較謎。按照資料,這個函數會檢查event_base中全部的active或pending的Event,而後調用傳入的一個回調,逐個調用。
注意這裏的callback只容許用來檢查event的狀態,不容許修改。此外,callback的執行時間不該太長,由於會有鎖保護。
爲何說這個函數謎呢?由於我實際上在libevent的頭文件中找-不-到這個函數啊!資料裏面是有的,可是.h文件中不存在,強行調用,也是找不到函數。從描述來看這個函數其實很好用,也很是適合初學者們跟蹤學習event_base及其event的狀態。可是……用不了。
Libevent的基本處理單元是event,每一個event表明一組條件:
當一個event被設置好,而且關聯到一個event_base裏面時,它被稱爲「initialized」。此時你能夠執行add
,這使得它進入pending
狀態。當event被觸發或超時時,它的狀態稱爲active
,這個狀況下對應的callback會被調用。若是event被配置爲persist
,那麼它在callback執行先後都會保持pending的狀態。能夠經過delete
來使得一個event從pending狀態從新變成nonpending
。
typedef void (*event_callback_fn) (evutil_socket_t, short, void *); struct event *event_new (struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg); void event_free (struct event *event);
建立一個新的event。其中fd是文件描述符,須要自行初始化以後再做爲參數傳入。event_free()
釋放event的資源。若是event是active或者是pending狀態,則函數會將event先變成非active且非pending的狀態,而後再釋放它。
參數what
表示這個event的須要關注綁定在該fd上的哪些事件。有如下幾個掩碼值:
signal
)int event_del (struct event *event);
前文所說的add
動做的反動做。EV_PERSIST使得一個event不會自動被event_base給delete掉,除非顯式地調用這個函數。
回調的時候會傳入自定義的arg。可是若是想要讓回調可以接受本身的struct event指針做爲參數,那麼應該使用如下函數:void *event_self_cbarg();
好比:
struct event *aEvent = event_new (pBase, aFd, EV_READ | EV_TIMEOUT, aCallback, event_self_cbarg());
Note on 2016-11-21: 在最新版本的 libevent 中,這個函數已經被停用了。而且目前並無這個函數的合適替代。
純超時事件不須要fd(傳入-1便可)。Libevent定義了一些方便的用於建立超時事件的宏:
evtimer_new (base, callback, arg) evtimer_add (ev, tv) evtimer_del (ev) evtimer_pending (ev, tv_out)
從測試結果來看,這裏的timer event都是一次性執行的,因此請自行在callback中從新add
信號時間也不須要fd,但不是傳入-1,而是對應的signum
。
evsignal_new (base, signum, callback, arg) // 自帶EV_PERSIST evsignal_add (ev, tv) evsignal_del (ev) evsignal_pending (ev, what, tv_out)
signal事件的callback是異步的,因此很安全,不像signal()
中的回調那樣有諸多限制。
注意:不要給signal事件設置timeout
int event_add (struct event *ev, const struct timeval *tv); int event_del (struct event *ev); int event_remove_timer (struct event *ev);
int event_priority_set (struct event *event, int priority);
將事件優先級設置在event base的優先級和0之間。
int event_pending (const struct event *ev, short what, struct timeval *tv_out);
注意這個函數的返回值其實是BOOL。函數判斷event是否爲給定的flag(what
)而被pending或active。若是tv_out
非空,而且EV_TIMEOUT
被設置了,那麼判斷完狀態後,event得timeout也會被經過這個參數返回回來。
如下是其餘一目瞭然的get函數:
evutil_short_t event_get_fd (const struct event *ev); struct event_base *event_get_base (const struct event *ev); // 注意這個很經常使用 short event_get_events (const struct event *ev); event_callback_fn event_get_callback (const struct event *ev); void *event_get_callback_arg (const struct event *ev); int event_get_priority (const struct event *ev); void event_get_assigement (const struct event *event, struct event_base **base_out, evutil_short_t *fd_out, short *events_out, event_callback_fn *callback_out, void *arg_out);
struct event *event_base_get_running_event (struct event_base *base);
注意這個函數只支持在event的loop中調用,在其它線程調用的話是不支持的
int event_base_once (struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg, const struct timeval *tv);
這個函數完成前面的event_new
和event_add
動做,而且不返回event對象。調用這個函數建立的對象直接加入到event_base中,而且執行了一次callback以後,自動delete
和free
掉。
Event的優先級默認,what不支持EV_SIGNAL和EV_PERSIST。
從libevent 2.1.2以後,即使一個event尚未被activate過,在event_base被釋放時,也會一併被釋放掉。在這以前這算是一個bug。不過也請注意其餘全部不禁libevent管理的資源(好比arg),要防止內存泄漏。
void event_active (struct event *ev, int what, short ncalls);
用指定的標誌來激活一個event,慎用該功能,特別是不要再callback中使用。但這是另外一個比較謎的函數,由於我在我本身設計的字符設備驅動中使用這個函數,可是實際上我監聽着這個設備的event卻根本沒有被激活。個人調用方式應該是沒問題的,難道是還須要內核驅動作什麼支持嗎?(2016-7-5 筆記:這裏說的可能只是不能在目標event的callback中使用,可是能夠在其它無關事件的callback中調用。尚未確認,可是猜想的可能性是:這個函數只有在libevent的loop中調用才能生效,不能異步地在loop外部activate。已經確認了在loop內部是生效的,至於在外部沒法生效的緣由,是否是我所猜想的那樣,目前我還找不到確認的答案)
Libevent對add/del超時事件具備 O(logN)的性能,使用與多個不一樣超時時間的隊列。可是若是有多個使用同一個超時值的事件,這樣作就顯得很低效了。此時libevent使用二進制堆算法(binary heep algorrithm)來完成這種任務。
此時libevent要涉及一個「common timeout
」了:
const struct timeval *event_base_init_common_timeout ( struct event_base *base, const struct timeval *duration);
這個函數返回一個持續的timeval指針,指向common timeout。使用這個timeout的事件會被加入到binary heep中,而且只有你指定的特定event_base中有效。返回的timeval指針,能夠把timeval值複製出來使用。
Libevent官方文檔學習筆記(1. libevent_core部分)(本文)
Libevent官方文檔學習筆記(2. bufferevent部分)
Libevent官方文檔學習筆記(3. evbuffer部分)