Libevent 官方文檔學習筆記(1. libevent_core部分)

初入libevent的人,極可能是第一次接觸異步編程。Libevent的編程思想,建議仍是多看前人的程序,或者是看libevent自己的文檔學習。
或者是介紹另一個庫,那就是libuv,它是libev某種意義上的的替代品(而libev又能夠算是libevent的某種替代品笑)。libuv的文檔我記得也有對異步編程的介紹。好了,這不是本文的內容。html

本文地址:https://segmentfault.com/a/1190000005359466算法

Reference

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,這使得它能夠很方便地用在一個閉源的項目裏面網絡

Libevent的特性

  1. evutil:不一樣平臺的抽象層,主要是網絡接口部分
  2. event, event_base:libevent的核心API
  3. bufferevent:緩存化的read/write接口,而且能夠與SSL互相封裝。

另外還有幾個我就不列出了,我的不多用異步

Libevent的庫

  1. libevent_core:Libevent的核心API和功能,包含了上述主要模塊
  2. libevent_extra:Libevent提供的額外功能包,這些目前還用不上
  3. libevent:包含上述兩個,可是建議之後不要使用

之因此還有event2文件夾,是由於這個文件夾下是新的(也就是目前使用的)libevent庫

建立一個event_base

每個event_base持有和管理多個event,而且判斷哪些event是被激活了的。可是這發生在一個單一的線程內。若是你想喲啊在多個線程內並行處理event,則須要在每一個線程中各建立一個event_base。

得到一個默認的event_base

struct event_base *event_base_new(void);

這個函數alloc並返回一個帶默認配置的event base。

得到一個複雜的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/O
EV_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;

設置event_base的分離時間

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`被設置成默認優先級

檢查event_base的backend方法

const char **event_get_supported_methods(void);

返回一個char*數組,說明了libevent支持的全部backend方法。數組以NULL結尾。

讀取指定event_base的配置

const char *event_base_get_method (const struct event_base *base);
enum event_method_feature event_base_get_features (const struct event_base *base);

釋放一個event_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 loop

當event_base和其中的event都配置好了以後,就能夠啓用libevent了。Event的配置在後文說,這裏先講event loop的運行。

運行loop

int event_base_loop (struct event_base *base, int flags);
int event_base_dispatch (struct event_base *base);

函數的行爲是:運行event_base,直到沒有event被註冊在event_base中位置。這是默認的行爲,也就是說,當沒有pendingactive事件時,函數返回。
  參數flags能夠用來修改默認行爲,有如下掩碼值:

  1. EVLOOP_ONCE:等待並執行active事件,循環執行到沒有事件可執行
  2. EVLOOP_NONBLOCK:循環不等待事件觸發,而是即檢查即回調
  3. EVLOOP_NO_EXIT:沒有事件仍不退出,而是由其餘函數觸發退出

第二個函數的做用是相同的,等效於使用默認的flags。

中止loop

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

檢查event_loop內部的時間

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當即同步更新時間。

將event_base的狀態存入文件

void event_base_dump_events (struct event_base *base, FILE *f);

實際上我以爲這個函數可以保存的信息仍是不夠多。另外,你能夠將/dev/stdout句柄打開以後傳入也是能夠的

在event_base中運行每個event

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的狀態。可是……用不了。

使用事件「event」

Libevent的基本處理單元是event,每一個event表明一組條件:

  1. 作好read或write準備的文件描述符
  2. 正在準備好read或write準備的文件描述符(邊沿觸發I/O,沒用過,不懂)
  3. 超時
  4. 信號被觸發
  5. 用戶觸發的事件

當一個event被設置好,而且關聯到一個event_base裏面時,它被稱爲「initialized」。此時你能夠執行add,這使得它進入pending狀態。當event被觸發或超時時,它的狀態稱爲active,這個狀況下對應的callback會被調用。若是event被配置爲persist,那麼它在callback執行先後都會保持pending的狀態。能夠經過delete來使得一個event從pending狀態從新變成nonpending

建立一個event對象

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上的哪些事件。有如下幾個掩碼值:

  • EV_TIMEOUT:超時
  • EV_READ:有數據可讀
  • EV_WRITE:數據可寫
  • EV_SIGNAL:系統發出的信號(signal
  • EV_PERSIST:持續事件
  • EV_ET:邊沿觸發

關於EV_PERSIST

int event_del (struct event *event);
前文所說的add動做的反動做。EV_PERSIST使得一個event不會自動被event_base給delete掉,除非顯式地調用這個函數。

使用event本身的ID做爲回調參數

回調的時候會傳入自定義的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

使事件進入pending和non-pending

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之間。

觀察event狀態

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);

尋找當前運行中event

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_newevent_add動做,而且不返回event對象。調用這個函數建立的對象直接加入到event_base中,而且執行了一次callback以後,自動deletefree掉。
  Event的優先級默認,what不支持EV_SIGNAL和EV_PERSIST。
  從libevent 2.1.2以後,即使一個event尚未被activate過,在event_base被釋放時,也會一併被釋放掉。在這以前這算是一個bug。不過也請注意其餘全部不禁libevent管理的資源(好比arg),要防止內存泄漏。

手動激活一個event

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部分)

相關文章
相關標籤/搜索