接口
int bufferevent_pair_new(struct event_base *base, int options,
struct bufferevent *pair[2]);
調用bufferevent_pair_new()會設置pair[0]和pair[1]為一對相互連接的bufferevent。除了BEV_OPT_CLOSE_ON_FREE無效、BEV_OPT_DEFER_CALLBACKS總是打開的之外,所有通常的選項都是支持的。
為什么bufferevent對需要帶延遲回調運行?通常某一方上的操作會調用一個通知另一方的回調,從而調用另一方的回調,如此這樣進行很多步。如果不延遲回調,這種調用鏈常常會導致棧溢出或者餓死其他連接,而且還要求所有的回調是可重入的。
成對的bufferevent支持flush:設置模式參數為BEV_NORMAL或者BEV_FLUSH會強制要求所有相關數據從對中的一個bufferevent傳輸到另一個中,而忽略可能會限制傳輸的水位設置。增加BEV_FINISHED到模式參數中還會讓對端的bufferevent產生EOF事件。
釋放對中的任何一個成員不會自動釋放另一個,也不會產生EOF事件。釋放僅僅會使對中的另一個成員成為斷開的。bufferevent一旦斷開,就不能再成功讀寫數據或者產生任何事件了。
接口
struct bufferevent *bufferevent_pair_get_partner(struct bufferevent *bev)
有時候在給出了對的一個成員時,需要獲取另一個成員,這時候可以使用bufferevent_pair_get_partner()。如果bev是對的成員,而且對的另一個成員仍然存在,函數將返回另一個成員;否則,函數返回NULL。
bufferevent對由2.0.1-alpha版本引入,而bufferevent_pair_get_partner()函數由2.0.6版本引入。
2 過濾bufferevent
有時候需要轉換傳遞給某bufferevent的所有數據,這可以通過添加一個壓縮層,或者將協議包裝到另一個協議中進行傳輸來實現。
接口
enum bufferevent_filter_result {
BEV_OK = 0,
BEV_NEED_MORE = 1,
BEV_ERROR = 2
};
typedef enum bufferevent_filter_result (*bufferevent_filter_cb)(
struct evbuffer *source, struct evbuffer *destination, ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode, void *ctx);
struct bufferevent *bufferevent_filter_new(struct bufferevent *underlying,
bufferevent_filter_cb input_filter,
bufferevent_filter_cb output_filter,
int options,
void (*free_context)(void *),
void *ctx);
bufferevent_filter_new()函數創建一個封裝現有的“底層”bufferevent的過濾bufferevent。所有通過底層bufferevent接收的數據在到達過濾bufferevent之前都會經過“輸入”過濾器的轉換;所有通過底層bufferevent發送的數據在被發送到底層bufferevent之前都會經過“輸出”過濾器的轉換。
向底層bufferevent添加過濾器將替換其回調函數。可以向底層bufferevent的evbuffer添加回調函數,但是如果想讓過濾器正確工作,就不能再設置bufferevent本身的回調函數。
input_filter和output_filter函數將隨后描述。options參數支持所有通常的選項。如果設置了BEV_OPT_CLOSE_ON_FREE,那么釋放過濾bufferevent也會同時釋放底層bufferevent。ctx參數是傳遞給過濾函數的任意指針;如果提供了free_context,則在釋放ctx之前它會被調用。
底層輸入緩沖區有數據可讀時,輸入過濾器函數會被調用;過濾器的輸出緩沖區有新的數據待寫入時,輸出過濾器函數會被調用。兩個過濾器函數都有一對evbuffer參數:從source讀取數據;向destination寫入數據,而dst_limit參數描述了可以寫入destination的字節數上限。過濾器函數可以忽略這個參數,但是這樣可能會違背高水位或者速率限制。如果dst_limit是-1,則沒有限制。mode參數向過濾器描述了寫入的方式。值BEV_NORMAL表示應該在方便轉換的基礎上寫入盡可能多的數據;而BEV_FLUSH表示寫入盡可能多的數據;BEV_FINISHED表示過濾器函數應該在流的末尾執行額外的清理操作。最后,過濾器函數的ctx參數就是傳遞給bufferevent_filter_new()函數的指針(ctx參數)。
如果成功向目標緩沖區寫入了任何數據,過濾器函數應該返回BEV_OK;如果不獲得更多的輸入,或者不使用不同的清空(flush)模式,就不能向目標緩沖區寫入更多的數據,則應該返回BEV_NEED_MORE;如果過濾器上發生了不可恢復的錯誤,則應該返回BEV_ERROR。
創建過濾器將啟用底層bufferevent的讀取和寫入。隨后就不需要自己管理讀取和寫入了:過濾器在不想讀取的時候會自動掛起底層bufferevent的讀取。從2.0.8-rc版本開始,可以在過濾器之外獨立地啟用/禁用底層bufferevent的讀取和寫入。然而,這樣可能會讓過濾器不能成功取得所需要的數據。
不需要同時指定輸入和輸出過濾器:沒有給定的過濾器將被一個不進行數據轉換的過濾器取代。
3 bufferevent和速率限制
某些程序需要限制單個或者一組bufferevent使用的帶寬。2.0.4-alpha和2.0.5-alpha版本添加了為單個或者一組bufferevent設置速率限制的基本功能。
3.1 速率限制模型
libevent的速率限制使用記號存儲器(token bucket)算法確定在某時刻可以寫入或者讀取多少字節。每個速率限制對象在任何給定時刻都有一個“讀存儲器(read bucket)”和一個“寫存儲器(write bucket)”,其大小決定了對象可以立即讀取或者寫入多少字節。每個存儲器有一個填充速率,一個最大突發尺寸,和一個時間單位,或者說“滴答(tick)”。一個時間單位流逝后,存儲器被填充一些字節(決定于填充速率)——但是如果超過其突發尺寸,則超出的字節會丟失。
因此,填充速率決定了對象發送或者接收字節的最大平均速率,而突發尺寸決定了在單次突發中可以發送或者接收的最大字節數;時間單位則確定了傳輸的平滑程度。
3.2 為bufferevent設置速率限制
接口
#define EV_RATE_LIMIT_MAX EV_SSIZE_MAX
struct ev_token_bucket_cfg;
struct ev_token_bucket_cfg *ev_token_bucket_cfg_new(
size_t read_rate, sizee_t read_burst,
size_t write_rate, size_t write_burst,
const struct timeval *tick_len);
void ev_token_bucket_cfg_free(struct ev_token_bucket_cfg *cfg);
int bufferevent_set_rate_limit(struct bufferevent *bev,
struct ev_token_bucket_cfg *cfg);
ev_token_bucket_cfg結構體代表用于限制單個或者一組bufferevent的一對記號存儲器的配置值。要創建ev_token_bucket_cfg,調用ev_token_bucket_cfg_new函數,提供最大平均讀取速率、最大突發讀取量、最大平均寫入速率、最大突發寫入量,以及一個滴答的長度。如果tick_len參數為NULL,則默認的滴答長度為一秒。如果發生錯誤,函數會返回NULL。
注意:read_rate和write_rate參數的單位是字節每滴答。也就是說,如果滴答長度是十分之一秒,read_rate是300,則最大平均讀取速率是3000字節每秒。此外,不支持大于EV_RATE_LIMIT_MAX的速率或者突發量。
要限制bufferevent的傳輸速率,使用一個ev_token_bucket_cfg,對其調用bufferevent_set_rate_limit()。成功時函數返回0,失敗時返回-1。可以對任意數量的bufferevent使用相同的ev_token_bucket_cfg。要移除速率限制,可以調用bufferevent_set_rate_limit(),傳遞NULL作為cfg參數值。
調用ev_token_bucket_cfg_free()可以釋放ev_token_bucket_cfg。注意:當前在沒有任何bufferevent使用ev_token_bucket_cfg之前進行釋放是不安全的。
3.3 為一組bufferevent設置速率限制
如果要限制一組bufferevent總的帶寬使用,可以將它們分配到一個速率限制組中。
接口
struct bufferevent_rate_limit_group;
struct bufferevent_rate_limit_group *bufferevent_rate_limit_group_new(
struct event_base *base,
const struct ev_token_bucket_cfg *cfg);
int bufferevent_rate_limit_group_set_cfg(
struct bufferevent_rate_limit_group *group,
const struct ev_token_bucket_cfg *cfg);
void bufferevent_rate_limit_group_free(struct bufferevent_rate_limit_group *);
int bufferevent_add_to_rate_limit_group(struct bufferevent *bev,
struct bufferevent_rate_limit_group *g);
int bufferevent_remove_from_rate_limit_group(struct bufferevent *bev);
要創建速率限制組,使用一個event_base和一個已經初始化的ev_token_bucket_cfg作為參數調用bufferevent_rate_limit_group_new函數。使用bufferevent_add_to_rate_limit_group將bufferevent添加到組中;使用bufferevent_remove_from_rate_limit_group從組中刪除bufferevent。這些函數成功時返回0,失敗時返回-1。
單個bufferevent在某時刻只能是一個速率限制組的成員。bufferevent可以同時有單獨的速率限制(通過bufferevent_set_rate_limit設置)和組速率限制。設置了這兩個限制時,對每個bufferevent,較低的限制將被應用。
調用bufferevent_rate_limit_group_set_cfg修改組的速率限制。函數成功時返回0,失敗時返回-1。bufferevent_rate_limit_group_free函數釋放速率限制組,移除所有成員。
在2.0版本中,組速率限制試圖實現總體的公平,但是具體實現可能在小的時間范圍內并不公平。如果你強烈關注調度的公平性,請幫助提供未來版本的補丁。
3.4 檢查當前速率限制
有時候需要得知應用到給定bufferevent或者組的速率限制,為此,libevent提供了函數:
接口
ev_ssize_t bufferevent_get_read_limit(struct bufferevent *bev);
ev_ssize_t bufferevent_get_write_limit(struct bufferevent *bev);
ev_ssize_t bufferevent_rate_limit_group_get_read_limit(
struct bufferevent_rate_limit_group *);
ev_ssize_t bufferevent_rate_limit_group_get_write_limit(
struct bufferevent_rate_limit_group *);
上述函數返回以字節為單位的bufferevent或者組的讀寫記號存儲器大小。注意:如果bufferevent已經被強制超過其配置(清空(flush)操作就會這樣),則這些值可能是負數。
接口
ev_ssize_t bufferevent_get_max_to_read(struct bufferevent *bev);
ev_ssize_t bufferevent_get_max_to_write(struct bufferevent *bev);
這些函數返回在考慮了應用到bufferevent或者組(如果有)的速率限制,以及一次最大讀寫數據量的情況下,現在可以讀或者寫的字節數。
接口
void bufferevent_rate_limit_group_get_totals(
struct bufferevent_rate_limit_group *grp,
ev_uint64_t *total_read_out, ev_uint64_t *total_written_out);
void bufferevent_rate_limit_group_reset_totals(
struct bufferevent_rate_limit_group *grp);
每個bufferevent_rate_limit_group跟蹤經過其發送的總的字節數,這可用于跟蹤組中所有bufferevent總的使用情況。對一個組調用bufferevent_rate_limit_group_get_totals會分別設置total_read_out和total_written_out為組的總讀取和寫入字節數。組創建的時候這些計數從0開始,調用bufferevent_rate_limit_group_reset_totals會復位計數為0。
3.5 手動調整速率限制
對于有復雜需求的程序,可能需要調整記號存儲器的當前值。比如說,如果程序不通過使用bufferevent的方式產生一些通信量時。
接口
int bufferevent_decrement_read_limit(struct bufferevent *bev, ev_ssize_t decr);
int bufferevent_decrement_write_limit(struct bufferevent *bev, ev_ssize_t decr);
int bufferevent_rate_limit_group_decrement_read(
struct bufferevent_rate_limit_group *grp, ev_ssize_t decr);
int bufferevent_rate_limit_group_decrement_write(
struct bufferevent_rate_limit_group *grp, ev_ssize_t decr);
這些函數減小某個bufferevent或者速率限制組的當前讀或者寫存儲器。注意:減小是有符號的。如果要增加存儲器,就傳入負值。
3.6 設置速率限制組的最小可能共享
通常,不希望在每個滴答中為速率限制組中的所有bufferevent平等地分配可用的字節。比如說,有一個含有10000個活動bufferevent的速率限制組,它在每個滴答中可以寫入10000字節,那么,因為系統調用和TCP頭部的開銷,讓每個bufferevent在每個滴答中僅寫入1字節是低效的。
為解決此問題,速率限制組有一個“最小共享(minimum share)”的概念。在上述情況下,不是允許每個bufferevent在每個滴答中寫入1字節,而是在每個滴答中允許某個bufferevent寫入一些(最小共享)字節,而其余的bufferevent將不允許寫入。允許哪個bufferevent寫入將在每個滴答中隨機選擇。
默認的最小共享值具有較好的性能,當前(2.0.6-rc版本)其值為64。可以通過這個函數調整最小共享值:
接口
int bufferevent_rate_limit_group_set_min_share(
struct bufferevent_rate_limit_group *group, size_t min_share);
設置min_share為0將會完全禁止最小共享。
速率限制功能從引入開始就具有最小共享了,而修改最小共享的函數在2.0.6-rc版本首次引入。
3.7 速率限制實現的限制
2.0版本的libevent的速率限制具有一些實現上的限制:
l 不是每種bufferevent類型都良好地或者說完整地支持速率限制。
l bufferevent速率限制組不能嵌套,一個bufferevent在某時刻只能屬于一個速率限制組。
l 速率限制實現僅計算TCP分組傳輸的數據,不包括TCP頭部。
l 讀速率限制實現依賴于TCP棧通知應用程序僅僅以某速率消費數據,并且在其緩沖區滿的時候將數據推送到TCP連接的另一端。
l 某些bufferevent實現(特別是Windows中的IOCP實現)可能調撥過度。
l 存儲器開始于一個滴答的通信量。這意味著bufferevent可以立即開始讀取或者寫入,而不用等待一個滴答的時間。但是這也意味著速率被限制為N.1個滴答的bufferevent可能傳輸N+1個滴答的通信量。
l 滴答不能小于1毫秒,毫秒的小數部分都被忽略。
4 bufferevent和SSL
bufferevent可以使用OpenSSL庫實現SSL/TLS安全傳輸層。因為很多應用不需要或者不想鏈接OpenSSL,這部分功能在單獨的libevent_openssl庫中實現。未來版本的libevent可能會添加其他SSL/TLS庫,如NSS或者GnuTLS,但是當前只有OpenSSL。
OpenSSL功能在2.0.3-alpha版本引入,然而直到2.0.5-beta和2.0.6-rc版本才能良好工作。
這一節不包含對OpenSSL、SSL/TLS或者密碼學的概述。
這一節描述的函數都在event2/bufferevent_ssl.h中聲明。
4.1 創建和使用基于OpenSSL的bufferevent
接口
enum bufferevent_ssl_state {
BUFFEREVENT_SSL_OPEN = 0,
BUFFEREVENT_SSL_CONNECTING = 1,
BUFFEREVENT_SSL_ACCEPTING = 2
};
struct bufferevent *
bufferevent_openssl_filter_new(struct event_base *base,
struct bufferevent *underlying,
SSL *ssl,
enum bufferevent_ssl_state state,
int options);
struct bufferevent *
bufferevent_openssl_socket_new(struct event_base *base,
evutil_socket_t fd,
SSL *ssl,
enum bufferevent_ssl_state state,
int options);
可以創建兩種類型的SSL bufferevent:基于過濾器的、在另一個底層bufferevent之上進行通信的buffervent;或者基于套接字的、直接使用OpenSSL進行網絡通信的bufferevent。這兩種bufferevent都要求提供SSL對象及其狀態描述。如果SSL當前作為客戶端在進行協商,狀態應該是BUFFEREVENT_SSL_CONNECTING;如果作為服務器在進行協商,則是BUFFEREVENT_SSL_ACCEPTING;如果SSL握手已經完成,則狀態是BUFFEREVENT_SSL_OPEN。
接受通常的選項。BEV_OPT_CLOSE_ON_FREE表示在關閉openssl bufferevent對象的時候同時關閉SSL對象和底層fd或者bufferevent。
創建基于套接字的bufferevent時,如果SSL對象已經設置了套接字,就不需要提供套接字了:只要傳遞-1就可以。也可以隨后調用bufferevent_setfd()來設置。
接口
SSL *bufferevent_openssl_get_ssl(struct bufferevent *bev);
這個函數返回OpenSSL bufferevent使用的SSL對象。如果bev不是一個基于OpenSSL的bufferevent,則返回NULL。
接口
unsigned long bufferevent_get_openssl_error(struct bufferevent *bev);
這個函數返回給定bufferevent的第一個未決的OpenSSL錯誤;如果沒有未決的錯誤,則返回0。錯誤值的格式與openssl庫中的ERR_get_error()返回的相同。
接口
int bufferevent_ssl_renegotiate(struct bufferevent *bev);
調用這個函數要求SSL重新協商,bufferevent會調用合適的回調函數。這是個高級功能,通常應該避免使用,除非你確實知道自己在做什么,特別是有些SSL版本具有與重新協商相關的安全問題。