- Bufferevents:概念和基礎知識
- Bufferevents 和 evbuffers
- 回撥和水印
- 延遲迴調
- 緩衝區事件的選項標誌
- 使用基於套接字的緩衝區事件
- 建立基於套接字的緩衝區事件
- 在基於套接字的緩衝區事件上啟動連線
- 按主機名啟動連線
- 通用 bufferevent 操作
- 釋放緩衝區事件
- 操作回撥、水印和啟用的操作
- 操作緩衝區事件中的資料
- 讀寫超時
- 在緩衝區事件上啟動重新整理
- 特定於型別的 bufferevent 函式
- 手動鎖定和解鎖緩衝區事件
- 過時的緩衝區事件功能
- Bufferevents:高階主題
- 配對緩衝區事件
- 篩選緩衝區事件
- 限制最大單次讀/寫大小
- 緩衝事件和速率限制
- 速率限制模型
- 設定緩衝事件的速率限制
- 為一組緩衝區事件設定速率限制
- 檢查當前速率限制值
- 手動調整速率限制
- 在速率受限的組中設定儘可能小的份額
- 速率限制實現的侷限性
- Bufferevents 和 SSL
- 設定和使用基於 OpenSSL 的緩衝區事件
- 關於執行緒和 OpenSSL 的一些說明
Bufferevents:概念和基礎知識
大多數情況下,應用程式希望執行一定數量的資料 除了僅響應事件之外,還進行緩衝。當我們想要的時候 寫入資料,例如,通常的模式執行如下:
- 決定我們要將一些資料寫入連線;把那個 緩衝區中的資料。
- 等待連線變為可寫
- 儘可能多地寫入資料
- 記住我們寫了多少,如果我們還有更多的資料要寫, 等待連線再次變為可寫。
這種緩衝 IO 模式很常見,以至於 Libevent 提供了一個 它的通用機制。“緩衝事件”由 底層傳輸(如套接字)、讀取緩衝區和寫入 緩衝區。而不是常規事件,這些事件在 基礎傳輸已準備好讀取或寫入,即 BufferEvent 在讀取或寫入足夠多的內容時呼叫其使用者提供的回撥 資料。
有多種型別的緩衝事件,它們都共享一個共同點 介面。在撰寫本文時,存在以下型別:
-
基於套接字的緩衝事件
從基礎傳送和接收資料的緩衝事件 stream socket,使用 event_* 介面作為其後端。
-
非同步 IO 緩衝區事件
使用 Windows IOCP 介面傳送和 將資料接收到基礎流套接字。(僅限 Windows; 實驗性的。
-
篩選緩衝區事件
之前處理傳入和傳出資料的緩衝事件 將其傳遞給基礎 BufferEvent 物件,例如,傳遞給 壓縮或翻譯資料。
-
配對緩衝事件
兩個相互傳輸資料的緩衝事件。
注意
從 Libevent 2.0.2-alpha 開始,這裡的 bufferevents 介面仍然是 在所有 BufferEvent 型別中不完全正交。換言之, 並非下面描述的每個介面都適用於所有 BufferEvent 型別。 Libevent 開發人員打算在未來的版本中糾正此問題。
另請注意
緩衝區事件目前僅適用於面向流的協議,如 TCP。 將來可能會支援面向資料包的協議,如 UDP。
本節中的所有函式和型別都在 event2/bufferevent.h檔案。與 evbuffers 特別相關的函式包括 在 event2/buffer.h 中宣告;有關以下方面的資訊,請參閱下一章 那些。
Bufferevents 和 evbuffers
每個緩衝區事件都有一個輸入緩衝區和一個輸出緩衝區。這些是 型別為“struct evbuffer”。當您有資料要寫入時 buffer事件,將其新增到輸出緩衝區;當 bufferevent 具有 資料供您讀取,請將其從輸入緩衝區中排出。
evbuffer 介面支援多種操作;我們討論它們 後面的部分。
回撥和水印
每個 bufferevent 都有兩個與資料相關的回撥:一個讀取回撥 和寫入回撥。預設情況下,讀取回撥被呼叫 每當從基礎傳輸中讀取任何資料時,寫入 每當輸出緩衝區中的足夠資料被清空時,就會呼叫回撥 基礎傳輸。您可以覆蓋這些函式的行為 透過調整 bufferevent 的讀寫“水印”。
每個緩衝事件都有四個水印:
-
讀取低水位線
每當發生離開 bufferevent 的輸入緩衝區的讀取時 在此級別或更高階別,將呼叫 BufferEvent 的 Read 回撥。 預設為 0,因此每次讀取都會產生讀取回撥被呼叫。
-
讀取高水位線
如果 bufferevent 的輸入緩衝區達到此級別,則 buffer事件停止讀取,直到從輸緩衝區,再次將我們帶到它下方。預設為無限制,所以 我們永遠不會因為輸入緩衝區的大小而停止讀取。
-
寫下低水位線
每當發生將我們帶到此級別或更低階別的寫入時,我們 呼叫寫入回撥。預設值為 0,因此寫入回撥 除非清空輸出緩衝區,否則不會呼叫。
-
寫高水位線
不直接由緩衝區事件使用,此水印可以具有特殊的 這意味著當 bufferevent 用作 另一個 BufferEvent。請參閱下面有關篩選緩衝區事件的說明。
bufferevent 還具有 “error” 或 “event” 回撥,該回撥將 呼叫以告知應用程式有關非面向資料的事件,例如 當連線關閉或發生錯誤時。以下事件 標誌的定義如下:
-
BEV_EVENT_READING
在對 bufferevent 執行讀取操作期間發生事件。看 其他標誌是哪個事件。
-
BEV_EVENT_WRITING
在對 bufferevent 執行寫入操作期間發生事件。看 其他標誌是哪個事件。
-
BEV_EVENT_ERROR
bufferevent 操作期間發生錯誤。檢視更多 有關錯誤的資訊,請呼叫 EVUTIL_SOCKET_ERROR()。
-
BEV_EVENT_TIMEOUT
緩衝事件的超時已過期。
-
BEV_EVENT_EOF
我們在 bufferevent 上得到了檔案結束指示。
-
BEV_EVENT_CONNECTED
我們在 bufferevent 上完成了請求的連線。
(上述事件名稱在 Libevent 2.0.2-alpha 中是新增的。
延遲迴調
預設情況下,在以下情況下會立即執行 bufferevent 回撥 發生相應的情況。(evbuffer 也是如此 回撥也是如此;我們稍後會談到這些。此即時呼叫 當依賴關係變得複雜時,可能會造成麻煩。例如,假設 有一個回撥,當資料增長時,它會將資料移動到 evbuffer A 中 empty,以及另一個從 evbuffer A 處理資料的回撥 它長得很飽滿。由於這些呼叫都發生在堆疊上,因此 如果依賴項變得足夠討厭,則可能會面臨堆疊溢位的風險。
為了解決這個問題,你可以告訴 bufferevent(或 evbuffer)它的 回撥應該被推遲。當滿足條件時 延遲迴調,而不是立即呼叫它,而是排隊 作為 event_loop() 呼叫的一部分,並在常規事件之後呼叫。 回撥。
(延遲迴調是在 Libevent 2.0.1-alpha 中引入的。
緩衝區事件的選項標誌
在建立 bufferevent 時,可以使用一個或多個標誌來更改其 行為。識別的標誌包括:
-
BEV_OPT_CLOSE_ON_FREE
釋放緩衝事件後,關閉基礎傳輸。 這將關閉底層套接字,釋放底層 bufferevent 等。
-
BEV_OPT_THREADSAFE
自動為 bufferevent 分配鎖,以便它 可從多個執行緒安全使用。
-
BEV_OPT_DEFER_CALLBACKS
設定此標誌後,bufferevent 會延遲其所有回撥, 如上所述。
-
BEV_OPT_UNLOCK_CALLBACKS
預設情況下,當 bufferevent 設定為執行緒安全時, 每當任何使用者提供 呼叫回撥。設定此選項可使 Libevent 釋出 bufferEvent 呼叫回撥時的鎖定。
(Libevent 2.0.5-beta 於 BEV_OPT_UNLOCK_CALLBACKS 年推出。其他選項 以上是 Libevent 2.0.1-alpha 中的新功能。
使用基於套接字的緩衝區事件
要使用的最簡單的緩衝區事件是基於套接字的型別。一個 基於 socket 的 bufferevent 使用 Libevent 的底層事件機制來 檢測底層網路套接字何時準備好讀取和/或寫入 操作,並使用底層網路呼叫(如 readv、writev、 WSASend 或 WSARecv) 來傳輸和接收資料。
建立基於套接字的緩衝區事件
可以使用以下方法建立基於套接字的緩衝區事件 bufferevent_socket_new():
介面
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
base 是 event_base,options 是 bufferevent 的位掩碼 選項(BEV_OPT_CLOSE_ON_FREE等)。fd 引數是一個 套接字的可選檔案描述符。如果出現以下情況,您可以將 fd 設定為 -1 您希望稍後設定檔案描述符。
提示 | [確保您提供給bufferevent_socket_new的套接字是 在非阻塞模式下。Libevent 提供了方便的方法 evutil_make_socket_nonblocking為此。 |
---|---|
此函式在成功時返回 bufferevent,在失敗時返回 NULL。
bufferevent_socket_new() 函式是在 Libevent 2.0.1-alpha 中引入的。
在基於套接字的緩衝區事件上啟動連線
如果 bufferevent 的套接字尚未連線,則可以啟動新的 連線。
介面
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen);
address 和 addrlen 引數與標準呼叫相同 connect() 中。如果 bufferevent 尚未設定套接字, 呼叫此函式會為其分配一個新的流套接字,並使 它不阻塞。
如果 bufferevent 確實已經有套接字,則呼叫 bufferevent_socket_connect() 告訴 Libevent 套接字不是 連線,並且在套接字上不執行任何讀取或寫入操作,直到 連線操作已成功。
在連線之前將資料新增到輸出緩衝區是可以的 做。
如果連線已成功啟動,則此函式返回 0,並且 -1 如果發生錯誤。
例
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#include <string.h>
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
if (events & BEV_EVENT_CONNECTED) {
/* We're connected to 127.0.0.1:8080. Ordinarily we'd do
something here, like start reading or writing. */
} else if (events & BEV_EVENT_ERROR) {
/* An error occured while connecting. */
}
}
int main_loop(void)
{
struct event_base *base;
struct bufferevent *bev;
struct sockaddr_in sin;
base = event_base_new();
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(8080); /* Port 8080 */
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
if (bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
/* Error starting connection */
bufferevent_free(bev);
return -1;
}
event_base_dispatch(base);
return 0;
}
bufferevent_socket_connect() 函式是在 libevent-2.0.2-alpha。 在此之前,您必須手動呼叫 connect() 在您的套接字上,以及連線的時間 完成後,BufferEvent 會將其報告為寫入。
請注意BEV_EVENT_CONNECTED,只有在啟動 使用 bufferevent_socket_connect() 嘗試 connect()。如果您呼叫 connect() 時,連線會報告為寫入。
如果您想自己呼叫 connect(),但仍然收到一個 BEV_EVENT_CONNECTED 事件 連線成功時,呼叫 bufferevent_socket_connect(bev, NULL, 0) 後 connect() 返回 -1 和 errno 等於 EAGAIN 或 EINPROGRESS。
此函式是在 Libevent 2.0.2-alpha 中引入的。
按主機名啟動連線
很多時候,您希望將解析主機名和連線到主機名結合起來 變成一個操作。有一個介面:
介面
int bufferevent_socket_connect_hostname(struct bufferevent *bev,
struct evdns_base *dns_base, int family, const char *hostname,
int port);
int bufferevent_socket_get_dns_error(struct bufferevent *bev);
此函式解析 DNS 名稱主機名,查詢 family 型別的地址。(允許的家庭型別為 AF_INET、AF_INET6 和 AF_UNSPEC。如果 名稱解析失敗,它會使用錯誤事件呼叫事件回撥。 如果成功,它會像bufferevent_connect一樣啟動連線嘗試 願意。
dns_base 引數是可選的。如果它為 NULL,則 Libevent 會阻塞 等待名稱查詢完成,這通常不是您想要的。如果 它提供了,然後 Libevent 使用它非同步查詢主機名。 有關 DNS 的更多資訊,請參閱第 R9 章。
與 bufferevent_socket_connect() 一樣,此函式告訴 Libevent 任何 BufferEvent 上的現有套接字未連線,並且沒有讀取或寫入 應該在套接字上完成,直到解析完成並連線 操作成功。
如果發生錯誤,則可能是 DNS 主機名查詢錯誤。你可以找到 透過呼叫找出最近的錯誤是什麼 bufferevent_socket_get_dns_error()。如果返回的錯誤程式碼為 0,則沒有 DNS 檢測到錯誤。
示例:簡單的 HTTP v0 客戶端。
/* Don't actually copy this code: it is a poor way to implement an
HTTP client. Have a look at evhttp instead.
*/
#include <event2/dns.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/event.h>
#include <stdio.h>
void readcb(struct bufferevent *bev, void *ptr)
{
char buf[1024];
int n;
struct evbuffer *input = bufferevent_get_input(bev);
while ((n = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
fwrite(buf, 1, n, stdout);
}
}
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
if (events & BEV_EVENT_CONNECTED) {
printf("Connect okay.\n");
} else if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) {
struct event_base *base = ptr;
if (events & BEV_EVENT_ERROR) {
int err = bufferevent_socket_get_dns_error(bev);
if (err)
printf("DNS error: %s\n", evutil_gai_strerror(err));
}
printf("Closing\n");
bufferevent_free(bev);
event_base_loopexit(base, NULL);
}
}
int main(int argc, char **argv)
{
struct event_base *base;
struct evdns_base *dns_base;
struct bufferevent *bev;
if (argc != 3) {
printf("Trivial HTTP 0.x client\n"
"Syntax: %s [hostname] [resource]\n"
"Example: %s www.google.com /\n",argv[0],argv[0]);
return 1;
}
base = event_base_new();
dns_base = evdns_base_new(base, 1);
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, NULL, eventcb, base);
bufferevent_enable(bev, EV_READ|EV_WRITE);
evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n", argv[2]);
bufferevent_socket_connect_hostname(
bev, dns_base, AF_UNSPEC, argv[1], 80);
event_base_dispatch(base);
return 0;
}
bufferevent_socket_connect_hostname() 函式是 Libevent 中的新功能 2.0.3-阿爾法;bufferevent_socket_get_dns_error() 是 2.0.5-beta 中的新功能。
通用 bufferevent 操作
本節中的函式適用於多個緩衝區事件 實現。
釋放緩衝區事件
介面
void bufferevent_free(struct bufferevent *bev);
此函式釋放緩衝區事件。緩衝區事件在內部 reference-counted,因此如果 bufferevent 有待處理的延遲 回撥 當你釋放它時,它不會被刪除,直到回撥 都完成了。
但是,bufferevent_free() 函式會嘗試釋放 Buffer事件。如果有待處理的資料要寫入 BufferEvent,它可能不會在 BufferEvent 之前重新整理 釋放。
如果設定了 BEV_OPT_CLOSE_ON_FREE 標誌,並且此 bufferevent 具有 套接字或與之關聯的底層緩衝事件作為其傳輸, 釋放 BufferEvent 時,該傳輸將關閉。
此函式是在 Libevent 0.8 中引入的。
操作回撥、水印和啟用的操作
介面
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev,
short events, void *ctx);
void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg);
void bufferevent_getcb(struct bufferevent *bufev,
bufferevent_data_cb *readcb_ptr,
bufferevent_data_cb *writecb_ptr,
bufferevent_event_cb *eventcb_ptr,
void **cbarg_ptr);
bufferevent_setcb() 函式更改一個或多個回撥 緩衝事件。readcb、writecb 和 eventcb 函式是 當讀取足夠多的資料時,呼叫(分別),當讀取足夠的資料時 寫入,或事件發生時。每個引數的第一個引數是 發生事件的 bufferEvent。最後一個引數是 使用者在 cbarg 引數中提供的值 bufferevent_callcb():你可以用它來將資料傳遞給你的 回撥。事件回撥的 events 引數是一個位掩碼 事件標誌:請參閱上面的“回撥和水印”。
您可以透過傳遞 NULL 而不是回撥來禁用回撥 功能。請注意,bufferevent 上的所有回撥函式都共享 單個 cbarg 值,因此更改它將影響所有值。
您可以透過傳送 bufferevent 來檢索當前設定的回撥 指向 bufferevent_getcb() 的指標,該指標將 readcb_ptr 設定為當前讀取 回撥,writecb_ptr當前寫入回撥,*eventcb_ptr 當前事件回撥,以及 *cbarg_ptr 當前回撥引數 田。任何設定為 NULL 的指標都將被忽略。
bufferevent_setcb() 函式是在 Libevent 1.4.4 中引入的。型別 名稱“bufferevent_data_cb”和“bufferevent_event_cb”在 Libevent 中是新的 2.0.2-阿爾法。bufferevent_getcb() 函式是在 2.1.1-alpha 中新增的。
介面
void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);
short bufferevent_get_enabled(struct bufferevent *bufev);
您可以啟用或禁用事件 EV_READ、EV_WRITE 或 EV_READ|EV_WRITE在緩衝事件上。當閱讀或寫作不是 啟用後,BufferEvent 將不會嘗試讀取或寫入資料。
當輸出緩衝區為空時,無需禁用寫入: BufferEvent 自動停止寫入,然後再次重新啟動 當有資料要寫入時。
同樣,當輸入緩衝區 達到其高水位線:緩衝事件自動停止 讀取,並在有閱讀空間時重新啟動。
預設情況下,新建立的 bufferevent 已啟用寫入功能,但未啟用 讀數。
您可以呼叫 bufferevent_get_enabled() 來檢視當前正在發生的事件 在 BufferEvent 上啟用。
這些函式是在 Libevent 0.8 中引入的,但 bufferevent_get_enabled(),在 2.0.3-alpha 版本中引入。
介面
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark);
bufferevent_setwatermark() 函式調整讀取水印, 寫入單個 BufferEvent 的水印,或同時寫入水印。(如果EV_READ 在事件欄位中設定,則調整讀取的水印。如果 EV_WRITE在事件欄位中設定,則會調整寫入水印。
高水位線 0 等同於“無限”。
此函式在 Libevent 1.4.4 中首次公開。
例
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
struct info {
const char *name;
size_t total_drained;
};
void read_callback(struct bufferevent *bev, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
if (len) {
inf->total_drained += len;
evbuffer_drain(input, len);
printf("Drained %lu bytes from %s\n",
(unsigned long) len, inf->name);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
int finished = 0;
if (events & BEV_EVENT_EOF) {
size_t len = evbuffer_get_length(input);
printf("Got a close from %s. We drained %lu bytes from it, "
"and have %lu left.\n", inf->name,
(unsigned long)inf->total_drained, (unsigned long)len);
finished = 1;
}
if (events & BEV_EVENT_ERROR) {
printf("Got an error from %s: %s\n",
inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
finished = 1;
}
if (finished) {
free(ctx);
bufferevent_free(bev);
}
}
struct bufferevent *setup_bufferevent(void)
{
struct bufferevent *b1 = NULL;
struct info *info1;
info1 = malloc(sizeof(struct info));
info1->name = "buffer 1";
info1->total_drained = 0;
/* ... Here we should set up the bufferevent and make sure it gets
connected... */
/* Trigger the read callback only whenever there is at least 128 bytes
of data in the buffer. */
bufferevent_setwatermark(b1, EV_READ, 128, 0);
bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);
bufferevent_enable(b1, EV_READ); /* Start reading. */
return b1;
}
操作緩衝區事件中的資料
從網路讀取和寫入資料對您沒有好處,如果您不能看它。Bufferevents 為您提供了這些方法來提供它們 要寫入的資料,以及要讀取的資料:
介面
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
這兩個函式是非常強大的基本函式:它們返回 分別輸入和輸出緩衝器。有關所有的完整資訊 可以對 EVPud 型別執行的操作,請參見下一篇 章。
請注意,應用程式只能從輸入中刪除(而不是新增)資料 緩衝區,並且只能向輸出緩衝區新增(而不是刪除)資料。
如果寫入緩衝事件由於資料太少而停止 (或者如果讀取因太多而停滯不前),然後將資料新增到 輸出緩衝區(或從輸入緩衝區中刪除資料)將 自動重新啟動它。
這些函式是在 Libevent 2.0.1-alpha 中引入的。
介面
int bufferevent_write(struct bufferevent *bufev,
const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev,
struct evbuffer *buf);
這些函式將資料新增到 bufferevent 的輸出緩衝區。叫 bufferevent_write() 將記憶體中的**大小位元組新增到 輸出緩衝區的末尾。呼叫 bufferevent_write_buffer() 刪除 BUF 的全部內容,並將它們放在輸出的末尾 緩衝區。如果成功,兩者都返回 0,如果發生錯誤,則返回 -1。
這些函式從 Libevent 0.8 開始就已經存在了。
介面
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev,
struct evbuffer *buf);
這些函式從 bufferevent 的輸入緩衝區中刪除資料。這 bufferevent_read() 函式從輸入中刪除最大size的位元組 緩衝區,將它們儲存在data儲存器中。它返回數字 實際刪除的位元組數。bufferevent_read_buffer() 函式 排出輸入緩衝區的全部內容並將它們放入 BUF;成功時返回 0,失敗時返回 -1。
請注意,使用 bufferevent_read(),資料中的記憶體塊必須 實際上有足夠的空間來容納大小位元組的資料。
bufferevent_read() 函式自 Libevent 0.8 以來就已存在; bufferevent_read_buffer() 是在 Libevent 2.0.1-alpha 中引入的。
例
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <ctype.h>
void
read_callback_uppercase(struct bufferevent *bev, void *ctx)
{
/* This callback removes the data from bev's input buffer 128
bytes at a time, uppercases it, and starts sending it
back.
(Watch out! In practice, you shouldn't use toupper to implement
a network protocol, unless you know for a fact that the current
locale is the one you want to be using.)
*/
char tmp[128];
size_t n;
int i;
while (1) {
n = bufferevent_read(bev, tmp, sizeof(tmp));
if (n <= 0)
break; /* No more data. */
for (i=0; i<n; ++i)
tmp[i] = toupper(tmp[i]);
bufferevent_write(bev, tmp, n);
}
}
struct proxy_info {
struct bufferevent *other_bev;
};
void
read_callback_proxy(struct bufferevent *bev, void *ctx)
{
/* You might use a function like this if you're implementing
a simple proxy: it will take data from one connection (on
bev), and write it to another, copying as little as
possible. */
struct proxy_info *inf = ctx;
bufferevent_read_buffer(bev,
bufferevent_get_output(inf->other_bev));
}
struct count {
unsigned long last_fib[2];
};
void
write_callback_fibonacci(struct bufferevent *bev, void *ctx)
{
/* Here's a callback that adds some Fibonacci numbers to the
output buffer of bev. It stops once we have added 1k of
data; once this data is drained, we'll add more. */
struct count *c = ctx;
struct evbuffer *tmp = evbuffer_new();
while (evbuffer_get_length(tmp) < 1024) {
unsigned long next = c->last_fib[0] + c->last_fib[1];
c->last_fib[0] = c->last_fib[1];
c->last_fib[1] = next;
evbuffer_add_printf(tmp, "%lu", next);
}
/* Now we add the whole contents of tmp to bev. */
bufferevent_write_buffer(bev, tmp);
/* We don't need tmp any longer. */
evbuffer_free(tmp);
}
讀寫超時
與其他事件一樣,如果某個事件,則可以呼叫超時 時間過去了,沒有任何資料成功 由 bufferevent 寫入或讀取。
介面
void bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *timeout_read, const struct timeval *timeout_write);
將超時設定為 NULL 應該會刪除它;然而在 Libevent 之前 2.1.2-alpha 這不適用於所有事件型別。(作為解決方法 舊版本,您可以嘗試將超時設定為多天間隔 和/或讓你的 eventCB 函式忽略BEV_TIMEOUT事件,而你不這樣做 想要他們。
如果 bufferevent 在嘗試讀取資料時至少等待 timeout_read 秒,則將觸發讀取超時。寫入 如果 bufferevent 在嘗試寫入資料時至少等待 timeout_write 秒,則將觸發超時。
請注意,只有當 bufferevent 想要時,超時才會計算在內 讀取或寫入。換言之,如果出現以下情況,則不會啟用讀取超時 在 bufferevent 上禁用讀取,或者如果輸入緩衝區已滿,則讀取被禁用 (在其高水位線)。同樣,未啟用寫入超時 如果寫入被禁用,或者沒有要寫入的資料。
當發生讀寫超時時,相應的讀寫 在 BufferEvent 上禁用操作。然後事件回撥是 使用任一 BEV_EVENT_TIMEOUT|BEV_EVENT_READING 或 BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING。
此函式自 Libevent 2.0.1-alpha 以來一直存在。它沒有表現 在 Libevent 2.0.4-alpha 之前,跨 bufferevent 型別保持一致。
在緩衝區事件上啟動重新整理
介面
int bufferevent_flush(struct bufferevent *bufev,
short iotype, enum bufferevent_flush_mode state);
重新整理 bufferevent 會告知 bufferevent 強制執行儘可能多的位元組數 儘可能從基礎傳輸中讀取或寫入, 忽略其他可能阻止他們的限制 正在寫。它的詳細功能取決於 buffer事件。
iotype 引數應為 EV_READ、EV_WRITE 或 EV_READ|EV_WRITE 指示是否應讀取、寫入或同時讀取和/或寫入的位元組 處理。狀態引數可能是BEV_NORMAL之一, BEV_FLUSH,或BEV_FINISHED。BEV_FINISHED表示另一個 應告知方不會再傳送資料;區別 BEV_NORMAL 和 BEV_FLUSH 之間取決於 buffer事件。
bufferevent_flush() 函式在失敗時返回 -1,如果沒有資料,則返回 0 已重新整理,如果某些資料已重新整理,則為 1。
目前(從 Libevent 2.0.5-beta 開始),bufferevent_flush() 只有 為某些 BufferEvent 型別實現。特別是,基於套接字 bufferEvents 沒有它。
特定於型別的 bufferevent 函式
並非所有 bufferevent 都支援這些 bufferevent 函式 型別。
介面
int bufferevent_priority_set(struct bufferevent *bufev, int pri);
int bufferevent_get_priority(struct bufferevent *bufev);
此函式將用於實現 bufev 的事件的優先順序調整為 pri。有關以下內容的更多資訊,請參見 event_priority_set() 優先 級。
此函式在成功時返回 0,在失敗時返回 -1。它適用於 僅限基於套接字的緩衝事件。
bufferevent_priority_set() 函式是在 Libevent 1.0 中引入的; bufferevent_get_priority() 直到 Libevent 2.1.2-alpha 才出現。
介面
int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
這些函式設定或返回基於 fd 的檔案描述符 事件。只有基於套接字的 bufferevents 支援 setfd()。兩者都返回 -1 失敗;setfd() 在成功時返回 0。
bufferevent_setfd() 函式是在 Libevent 1.4.4 中引入的; bufferevent_getfd() 函式是在 Libevent 2.0.2-alpha 中引入的。
介面
struct event_base *bufferevent_get_base(struct bufferevent *bev);
此函式返回緩衝區事件的event_base。它被引入 2.0.9-rc。
介面
struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
此函式返回另一個 bufferevent 所在的 bufferevent 用作交通工具(如果有)。有關何時出現這種情況的資訊 ,請參閱有關篩選緩衝區事件的說明。
此函式是在 Libevent 2.0.2-alpha 中引入的。
手動鎖定和解鎖緩衝區事件
與 evbuffers 一樣,有時您希望確保許多操作 在緩衝區上,事件都是以原子方式執行的。Libevent 公開函式 可用於手動鎖定和解鎖 BufferEvent。
介面
void bufferevent_lock(struct bufferevent *bufev);
void bufferevent_unlock(struct bufferevent *bufev);
請注意,如果 bufferevent 不是 給定建立時的BEV_OPT_THREADSAFE執行緒,或者如果 Libevent 的執行緒 未啟用支援。
使用此函式鎖定 bufferevent 將鎖定其關聯的 evbuffers 也。這些函式是遞迴的:鎖定 bufferevent 是安全的 你已經拿著鎖了。當然,您必須呼叫一次解鎖 每次鎖定 BufferEvent。
這些函式是在 Libevent 2.0.6-rc 中引入的。
過時的緩衝區事件功能
bufferevent 後端程式碼在 Libevent 1.4 和 Libevent 2.0。在舊介面中,有時是 正常構建並訪問結構的內部結構 buffer事件,並使用依賴於此訪問的宏。
為了使事情變得混亂,舊程式碼有時會使用 以“evbuffer”為字首的 bufferEvent 功能。
以下是以前稱呼事物的簡要指南 Libevent 2.0:
現用名 | 舊名稱 |
---|---|
bufferevent_data_cb | evbuffercb |
bufferevent_event_cb | everrorcb |
BEV_EVENT_READING | EVBUFFER_READ |
BEV_EVENT_WRITE | EVBUFFER_WRITE |
BEV_EVENT_EOF | EVBUFFER_EOF |
BEV_EVENT_ERROR | EVBUFFER_ERROR |
BEV_EVENT_TIMEOUT | EVBUFFER_TIMEOUT |
bufferevent_get_input(二) | EVBUFFER_INPUT(二) |
bufferevent_get_output(二) | EVBUFFER_OUTPUT(二) |
舊函式是在 event.h 中定義的,而不是在 event2/bufferevent.h 中定義的。
如果您仍然需要訪問公共部件的內部 bufferEvent 結構,可以包含 event2/bufferevent_struct.h。我們 建議不要這樣做:struct bufferevent 的內容將在 Libevent 的版本。在以下情況下,本節中的宏和名稱可用 包括 event2/bufferevent_compat.h。
設定緩衝區事件的介面在舊版本中有所不同:
介面
struct bufferevent *bufferevent_new(evutil_socket_t fd,
evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg);
int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);
bufferevent_new() 函式僅建立套接字緩衝區事件,並這樣做 在已棄用的“預設”event_base上。呼叫bufferevent_base_set調整 僅套接字 Buffer 事件的event_base。
沒有將超時設定為結構時間,而是將其設定為 秒數:
介面
void bufferevent_settimeout(struct bufferevent *bufev,
int timeout_read, int timeout_write);
最後,請注意 Libevent 的底層 evbuffer 實現 2.0 之前的版本效率非常低,以至於使用 高效能應用程式的 BufferEvents 有點值得懷疑。
最後更新 世界協調時 2024-02-18 20:10:44
Bufferevents:高階主題
這些檔案版權所有 (c) 2009-2012 由 Nick Mathewson 製作 可在知識共享署名-非商業性使用-相同方式共享下使用 許可證,版本 3.0。未來的版本可能會在 限制性許可證。
此外,這些文件中的原始碼示例也已獲得許可 在所謂的“3-Clause”或“Modified”BSD許可證下。請參閱隨這些文件一起分發的 license_bsd 檔案 對於完整的條款。
若要獲取本文件最新版本的原始碼,請安裝 git 並執行“git clone git://github.com/libevent/libevent-book.git”
本章介紹了 Libevent 緩衝事件的一些高階功能 對於典型用途不是必需的實現。如果你只是 學習如何使用 BufferEvents,您應該暫時跳過本章 並繼續閱讀 EVBUFFER 章節。
配對緩衝區事件
有時,您有一個需要自言自語的網路程式。 例如,您可以編寫一個程式來隧道使用者連線 透過某些協議,有時還希望透過隧道連線 它自己在該協議上。您可以透過開啟一個 連線到您自己的收聽埠並讓您的程式使用 當然,它本身,但這會因為擁有你的程式而浪費資源 透過網路堆疊自言自語。
相反,您可以建立一對成對的緩衝區事件,以便所有位元組 一個上寫的在另一個上收到(反之亦然),但沒有實際的 使用平臺套接字。
介面
int bufferevent_pair_new(struct event_base *base, int options,
struct bufferevent *pair[2]);
呼叫 bufferevent_pair_new() 將 pair[0] 和 pair[1] 設定為一對 Buffer事件,每個事件都連線到另一個。所有常用的選項都是 支援,但 BEV_OPT_CLOSE_ON_FREE 除外,它沒有效果,並且 BEV_OPT_DEFER_CALLBACKS,它總是線上的。
為什麼 bufferevent 對需要在延遲迴調的情況下執行?很漂亮 對對中的一個元素執行的操作通常用於呼叫回撥 更改 BufferEvent,從而呼叫其他 BufferEvent 的回撥,以及 以此類推,透過許多步驟。當回撥沒有被延遲時,這個鏈 的呼叫會經常溢位堆疊,餓死其他 連線,並要求所有回撥都是可重入的。
配對的緩衝區事件支援重新整理;將 mode 引數設定為任一 BEV_NORMAL或BEV_FLUSH強制所有相關資料獲得 從對中的一個 BufferEvent 轉移到另一個 BufferEvent,忽略 否則會限制它的水印。將模式設定為BEV_FINISHED 此外,在相反的緩衝區事件上生成 EOF 事件。
釋放對中的任何一個成員不會自動釋放另一個成員或 生成 EOF 事件;它只是讓這對的另一名成員成為 未連結。一旦 bufferevent 被取消連結,它將不再成功 讀取或寫入資料或生成任何事件。
介面
struct bufferevent *bufferevent_pair_get_partner(struct bufferevent *bev)
有時,您可能需要給定 bufferevent 對的其他成員 只有一個成員。為此,您可以呼叫 bufferevent_pair_get_partner() 函式。它將返回 如果 BEV 是一對的成員,則該對仍然存在。 否則,它將返回 NULL。
Bufferevent 對是 Libevent 2.0.1-alpha 中的新增功能;這 bufferevent_pair_get_partner() 函式是在 Libevent 2.0.6 中引入的。
篩選緩衝區事件
有時,您希望轉換透過緩衝區事件傳遞的所有資料 物件。您可以這樣做來新增壓縮層,或將協議包裝在 另一種運輸協議。
介面
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 之前使用“input”篩選器進行轉換 到達 Filtering BufferEvent,以及透過 Filtering 傳送的所有資料 buffer事件在傳送到 基礎 BufferEvent。
將篩選器新增到基礎 bufferevent 會替換 基礎 bufferevent。您仍然可以向基礎新增回撥 bufferEvent 的 evbuffers,但無法在 bufferevent 上設定回撥 本身,如果您希望過濾器仍然有效。
下面介紹了 input_filter 和 output_filter 函式。 選項中支援所有常用選項。如果BEV_OPT_CLOSE_ON_FREE ,則釋放篩選緩衝區事件也會釋放基礎 buffer事件。ctx 欄位是傳遞給過濾器的任意指標 功能;如果提供了free_context函式,則僅在 CTX 上呼叫它 在關閉篩選 BufferEvent 之前。
每當有新的可讀資料時,都會呼叫輸入過濾器函式 在基礎輸入緩衝區上。輸出過濾器函式被呼叫 每當篩選器的輸出緩衝區上有新的可寫資料時。每一個 接收一對 EVBUFFER:一個用於從中讀取資料的源 evbuffer,以及一個用於將資料寫入的目標 evbuffer。dst_limit引數描述了 要新增到目標的位元組的上限。過濾功能為 允許忽略此值,但這樣做可能會違反高水位線 或速率限制。如果 dst_limit 為 -1,則沒有限制。mode 引數告訴篩選器在寫入時要有多激進。如果是 BEV_NORMAL,那麼它應該儘可能多地寫入可以方便地轉換。 BEV_FLUSH值意味著儘可能多地寫入,並BEV_FINISHED 表示過濾功能還應執行任何清理 在流的末尾是必需的。最後,filter 函式的 ctx 引數是提供給 bufferevent_filter_new() 的 void 指標 構造 函式。
篩選器函式必須返回BEV_OK是否有任何資料已成功寫入 目標緩衝區,BEV_NEED_MORE如果沒有更多資料可以寫入 目標緩衝區,無需獲取更多輸入或使用不同的重新整理 模式,並BEV_ERROR篩選器上是否存在不可恢復的錯誤。
建立篩選器可以在基礎上進行讀取和寫入 buffer事件。您不需要自行管理讀取/寫入:過濾器 每當它 不想看。對於 2.0.8-rc 及更高版本,允許 啟用/禁用對基礎 Buffer 事件的讀取和寫入 獨立於過濾器。但是,如果您這樣做,則可以保留 篩選成功獲取所需資料。
您無需同時指定輸入篩選器和輸出篩選器:any 省略的過濾器將替換為傳遞資料而不進行轉換的過濾器 它。
限制最大單次讀/寫大小
預設情況下,bufferevents 不會讀取或寫入可能的最大數量 每次呼叫事件迴圈的位元組數;這樣做會導致 奇怪的不公平行為和資源匱乏。另一方面, 預設值可能並非在所有情況下都合理。
介面
int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size);
int bufferevent_set_max_single_write(struct bufferevent *bev, size_t size);
ev_ssize_t bufferevent_get_max_single_read(struct bufferevent *bev);
ev_ssize_t bufferevent_get_max_single_write(struct bufferevent *bev);
兩個“set”函式取代了當前的讀寫最大值 分別。如果大小值為 0 或高於 EV_SSIZE_MAX,則 而是將最大值設定為預設值。這些函式返回 0 成功時為 -1,失敗時為 -1。
兩個“get”函式返回當前每迴圈的讀取和寫入 最大值。
這些函式是在 2.1.1-alpha 中新增的。
緩衝事件和速率限制
某些程式希望限制用於任何單個的頻寬量 bufferevent,或一組 bufferEvents。Libevent 2.0.4-alpha 和 Libevent 2.0.5-alpha 新增了一個基本功能,可以對個人進行上限 BufferEvents,或將 BufferEvents 分配給速率限制組。
速率限制模型
Libevent 的速率限制使用令牌桶演算法來決定數量 一次讀取或寫入的位元組數。每個受速率限制的物件,在任何給定 time,有一個“讀桶”和一個“寫桶”,其大小決定了 允許物件立即讀取或寫入的位元組數。每 桶具有重新填充率、最大突發大小和 定時單位或“滴答聲”。每當計時單元經過時,剷鬥就會重新裝滿 與再填充率成正比,但如果會變得比其爆發率更滿 大小,則任何多餘的位元組都會丟失。
因此,再填充速率決定了物體的最大平均速率 將傳送或接收位元組,突發大小決定最大數量 將在單個突發中傳送或接收的位元組數。計時單元 決定了交通的平滑度。
設定緩衝事件的速率限制
介面
#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, size_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 或一組 BufferEvents。要建立一個,請呼叫 ev_token_bucket_cfg_new函式並提供最大平均讀取速率, 最大讀取突發、最大寫入速率、最大寫入突發和 刻度的長度。如果 tick_len 引數為 NULL,則刻度的長度 預設值為一秒。該函式可能會在出錯時返回 NULL。
請注意,read_rate 和 write_rate 引數以 每個刻度的位元組數。也就是說,如果刻度是十分之一秒,read_rate是 300,則最大平均讀取速率為每 3000 位元組 第二。不支援超過 EV_RATE_LIMIT_MAX 的速率和突發值。
要限制緩衝事件的傳輸速率,請呼叫 bufferevent_set_rate_limit() 它有一個ev_token_bucket_cfg。該函式在成功時返回 0,在成功時返回 -1 失敗。您可以為任意數量的緩衝區事件提供相同的內容 ev_token_bucket_cfg。要刪除緩衝事件的速率限制,請呼叫 bufferevent_set_rate_limit(),為 cfg 引數傳遞 NULL。
要釋放ev_token_bucket_cfg,請呼叫 ev_token_bucket_cfg_free()。請注意, 目前,在沒有緩衝事件使用 ev_token_bucket_cfg。
為一組緩衝區事件設定速率限制
如果要限制,可以將緩衝區事件分配給速率限制組 他們的總頻寬使用量。
介面
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);
要構造速率限制組,bufferevent_rate_limit_group請使用 event_base和最初的ev_token_bucket_cfg。您可以將 bufferevents 新增到 具有 bufferevent_add_to_rate_limit_group() 和 bufferevent_remove_from_rate_limit_group();這些函式返回 0 成功和 -1 錯誤。
單個緩衝區事件可以是不超過一個速率限制組的成員 一次。緩衝事件可以同時具有單獨的速率限制(如 bufferevent_set_rate_limit()) 和組速率限制。當兩個限制都是 設定時,每個 BufferEvent 的下限適用。
您可以透過呼叫來更改現有組的速率限制 bufferevent_rate_limit_group_set_cfg()。成功時返回 0,成功時返回 -1 失敗。bufferevent_rate_limit_group_free() 函式釋放速率限制 組並刪除其所有成員。
從 2.0 版開始,Libevent 的組速率限制試圖公平 聚合,但在非常小的時間尺度上實施可能是不公平的。如果 您非常關心日程安排的公平性,請幫忙 帶有未來版本的補丁。
檢查當前速率限制值
有時,您的程式碼可能想要檢查應用的當前速率限制 對於給定的 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 可以執行此操作。
介面
ev_ssize_t bufferevent_get_max_to_read(struct bufferevent *bev);
ev_ssize_t bufferevent_get_max_to_write(struct bufferevent *bev);
這些函式返回 bufferevent 的位元組數 願意立即讀取或寫入,同時考慮到任何速率限制 適用於 BufferEvent、其速率限制組(如果有)和任何 Libevent 作為一個整體施加的最大讀/寫值。
介面
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跟蹤傳送的總位元組數 它,總共。您可以使用它來跟蹤總使用量 Buffer事件。叫 組上的 bufferevent_rate_limit_group_get_totals() 將 *total_read_out 和 *total_written_out 設定為在 BufferEvent 組。當組為 建立,並在 bufferevent_rate_limit_group_reset_totals() 時重置為 0 在組上被呼叫。
手動調整速率限制
對於具有真正複雜需求的程式,您可能需要調整當前 令牌儲存桶的值。您可能希望這樣做,例如,如果您的 程式以某種方式生成流量,而不是透過 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);
這些函式遞減緩衝區事件中的當前讀取或寫入儲存桶,或者 速率限制組。請注意,遞減是有符號的:如果要 遞增一個儲存桶,傳遞一個負值。
在速率受限的組中設定儘可能小的份額
通常,您不希望在速率限制中劃分可用的位元組 在每個刻度的所有緩衝事件中均勻分組。例如,如果您 在具有 10,000 位元組的速率限制組中有 10,000 個活動緩衝區事件 可用於寫入每個刻度,讓每個刻度都不會有效 由於系統呼叫的開銷,BufferEvent 每次呼叫僅寫入 1 個位元組 和 TCP 標頭。
為了解決這個問題,每個限速組都有一個“最小份額”的概念。 在上面的情況下,而不是允許每個緩衝事件寫入 1 位元組/刻度,允許 10,000/SHARE 緩衝事件寫入 SHARE 位元組 每個刻度,其餘的將被允許不寫任何東西。哪 Buffer允許先寫入的事件是每個刻度隨機選擇的。
選擇預設最小份額以提供不錯的效能,並且是 當前(截至 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 將完全禁用最小共享程式碼。
Libevent的速率限制自首次以來就具有最低份額 介紹。更改它們的函式首先在 Libevent 中公開 2.0.6-rc。
速率限制實現的侷限性
從 Libevent 2.0 開始,速率限制存在一些限制 你應該知道的實現。
- 並非每種 bufferevent 型別都支援速率限制,或者根本不支援速率限制。
- Bufferevent 速率限制組不能巢狀,而 bufferevent 只能是 一次在單個速率限制組中。
- 速率限制實現僅計算在 TCP 中傳輸的位元組數 資料包作為資料,不包括 TCP 標頭。
- 讀取限制實現依賴於 TCP 堆疊,注意到 應用程式僅以一定的速度消耗資料,然後推回 當 TCP 連線的緩衝區已滿時,TCP 連線的另一端。
- 緩衝區事件的一些實現(特別是 Windows IOCP implementation)可能會過度承諾。
- 儲存桶開始時有一整塊流量。這意味著 buffer事件可以立即開始讀取或寫入,而不是等到 完整的勾號已經過去了。不過,這也意味著具有 N.1 個報價的速率限制可能會轉移 N+1 個報價價值 交通。
- 刻度不能小於 1 毫秒,並且 毫秒被忽略。
TODO:寫一個限速的例子
Bufferevents 和 SSL
Bufferevents 可以使用 OpenSSL 庫來實現 SSL/TLS 安全 傳輸層。因為許多應用程式不需要或不想連結 OpenSSL,此功能在單獨的庫中實現,安裝為 “libevent_openssl”。Libevent 的未來版本可以新增對其他 SSL/TLS 庫,例如 NSS 或 GnuTLS,但現在 OpenSSL 就是全部 那裡。
OpenSSL 功能是在 Libevent 2.0.3-alpha 中引入的,儘管它沒有 在 Libevent 2.0.5-beta 或 Libevent 2.0.6-rc 之前執行良好。
本節不是關於 OpenSSL、SSL/TLS 或一般加密的教程。
這些函式都在標頭“event2/bufferevent_ssl.h”中宣告。
設定和使用基於 OpenSSL 的緩衝區事件
介面
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 緩衝區事件:一種是基於篩選器的緩衝區事件,它 透過另一個基礎緩衝區事件或基於套接字的 Buffer Event 進行通訊 buffer事件,告訴 OpenSSL 直接透過網路進行通訊。在 無論哪種情況,都必須提供 SSL 物件和 SSL 的描述 物件的狀態。如果 SSL 是BUFFEREVENT_SSL_CONNECTING 目前作為客戶進行談判,BUFFEREVENT_SSL_ACCEPTING 如果 SSL 當前正在作為伺服器執行協商,或者 BUFFEREVENT_SSL_OPEN SSL 握手是否完成。
接受通常的選項;BEV_OPT_CLOSE_ON_FREE使 SSL 物件 當 openSSL BufferEvent 關閉時,基礎 FD 或 BufferEvent 將關閉 本身是封閉的。
握手完成後,將呼叫新 bufferevent 的事件回撥 旗幟中BEV_EVENT_CONNECTED。
如果已建立基於套接字的緩衝區事件和 SSL 物件 有套接字,不需要自己提供套接字:透過就行了 -1.您也可以稍後使用 bufferevent_setfd() 設定 fd。
TODO:bufferevent_shutdown() API 完成後將其刪除。
請注意,當在 SSL bufferevent 上設定BEV_OPT_CLOSE_ON_FREE時, 不會對 SSL 連線執行乾淨關機。這有兩個 問題:首先,連線似乎已經被對方“破壞” 一方,而不是被關閉乾淨:另一方不會 能夠判斷是否關閉了連線,或者是否已斷開連線 由攻擊者或第三方提供。其次,OpenSSL將處理 會話為“壞”,並從會話快取中刪除。這 可能會導致負載下的 SSL 應用程式效能顯著下降。
目前,唯一的解決方法是手動執行延遲SSL關閉。雖然這 中斷 TLS RFC,它將確保會話將保留在 快取一旦關閉。下面的程式碼實現此變通辦法。
例
SSL *ctx = bufferevent_openssl_get_ssl(bev);
/*
* SSL_RECEIVED_SHUTDOWN tells SSL_shutdown to act as if we had already
* received a close notify from the other end. SSL_shutdown will then
* send the final close notify in reply. The other end will receive the
* close notify and send theirs. By this time, we will have already
* closed the socket and the other end's real close notify will never be
* received. In effect, both sides will think that they have completed a
* clean shutdown and keep their sessions valid. This strategy will fail
* if the socket is not ready for writing, in which case this hack will
* lead to an unclean shutdown and lost session on the other end.
*/
SSL_set_shutdown(ctx, SSL_RECEIVED_SHUTDOWN);
SSL_shutdown(ctx);
bufferevent_free(bev);
介面
SSL *bufferevent_openssl_get_ssl(struct bufferevent *bev);
此函式返回 OpenSSL 緩衝區事件或 NULL 使用的 SSL 物件 如果 bev 不是基於 OpenSSL 的緩衝事件。
介面
unsigned long bufferevent_get_openssl_error(struct bufferevent *bev);
此函式返回給定的第一個掛起的 OpenSSL 錯誤 bufferEvent 的操作,如果沒有掛起的錯誤,則為 0。錯誤 format 由 openssl 庫中的 ERR_get_error() 返回。
介面
int bufferevent_ssl_renegotiate(struct bufferevent *bev);
呼叫此函式會告訴 SSL 重新協商,並將 bufferevent 告知 呼叫適當的回撥。這是一個高階主題;你應該 除非你真的知道自己在做什麼,否則通常避免它,尤其是因為 許多 SSL 版本都存在與以下相關的已知安全問題 重新談判。
介面
int bufferevent_openssl_get_allow_dirty_shutdown(struct bufferevent *bev);
void bufferevent_openssl_set_allow_dirty_shutdown(struct bufferevent *bev,
int allow_dirty_shutdown);
SSL 協議的所有良好版本(即 SSLv3 和所有 TLS) versions) 支援經過身份驗證的關機操作,該操作啟用 當事方區分故意關閉與意外關閉或 在底層緩衝區中惡意誘導終止。預設情況下,我們 將正確關機以外的任何內容視為連線錯誤。 但是,如果 allow_dirty_shutdown 標誌設定為 1,則我們處理收盤價 在連線中作為BEV_EVENT_EOF。
allow_dirty_shutdown函式是在 Libevent 2.1.1-alpha 中新增的。
示例:基於 SSL 的簡單回顯伺服器
/* Simple echo server using OpenSSL bufferevents */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <event.h>
#include <event2/listener.h>
#include <event2/bufferevent_ssl.h>
static void
ssl_readcb(struct bufferevent * bev, void * arg)
{
struct evbuffer *in = bufferevent_get_input(bev);
printf("Received %zu bytes\n", evbuffer_get_length(in));
printf("----- data ----\n");
printf("%.*s\n", (int)evbuffer_get_length(in), evbuffer_pullup(in, -1));
bufferevent_write_buffer(bev, in);
}
static void
ssl_acceptcb(struct evconnlistener *serv, int sock, struct sockaddr *sa,
int sa_len, void *arg)
{
struct event_base *evbase;
struct bufferevent *bev;
SSL_CTX *server_ctx;
SSL *client_ctx;
server_ctx = (SSL_CTX *)arg;
client_ctx = SSL_new(server_ctx);
evbase = evconnlistener_get_base(serv);
bev = bufferevent_openssl_socket_new(evbase, sock, client_ctx,
BUFFEREVENT_SSL_ACCEPTING,
BEV_OPT_CLOSE_ON_FREE);
bufferevent_enable(bev, EV_READ);
bufferevent_setcb(bev, ssl_readcb, NULL, NULL, NULL);
}
static SSL_CTX *
evssl_init(void)
{
SSL_CTX *server_ctx;
/* Initialize the OpenSSL library */
SSL_load_error_strings();
SSL_library_init();
/* We MUST have entropy, or else there's no point to crypto. */
if (!RAND_poll())
return NULL;
server_ctx = SSL_CTX_new(SSLv23_server_method());
if (! SSL_CTX_use_certificate_chain_file(server_ctx, "cert") ||
! SSL_CTX_use_PrivateKey_file(server_ctx, "pkey", SSL_FILETYPE_PEM)) {
puts("Couldn't read 'pkey' or 'cert' file. To generate a key\n"
"and self-signed certificate, run:\n"
" openssl genrsa -out pkey 2048\n"
" openssl req -new -key pkey -out cert.req\n"
" openssl x509 -req -days 365 -in cert.req -signkey pkey -out cert");
return NULL;
}
SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv2);
return server_ctx;
}
int
main(int argc, char **argv)
{
SSL_CTX *ctx;
struct evconnlistener *listener;
struct event_base *evbase;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(9999);
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
ctx = evssl_init();
if (ctx == NULL)
return 1;
evbase = event_base_new();
listener = evconnlistener_new_bind(
evbase, ssl_acceptcb, (void *)ctx,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 1024,
(struct sockaddr *)&sin, sizeof(sin));
event_base_loop(evbase, 0);
evconnlistener_free(listener);
SSL_CTX_free(ctx);
return 0;
}
關於執行緒和 OpenSSL 的一些說明
Libevent 的內建執行緒機制不包括 OpenSSL 鎖定。 由於 OpenSSL 使用無數的全域性變數,因此您仍必須進行配置 OpenSSL 是執行緒安全的。雖然此過程超出了 Libevent 的範圍, 這個話題足以引起討論。
示例:如何啟用執行緒安全 OpenSSL 的非常簡單的示例
/*
* Please refer to OpenSSL documentation to verify you are doing this correctly,
* Libevent does not guarantee this code is the complete picture, but to be used
* only as an example.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <openssl/ssl.h>
#include <openssl/crypto.h>
pthread_mutex_t * ssl_locks;
int ssl_num_locks;
#ifndef WIN32
#define _SSLtid (unsigned long)pthread_self()
#else
#define _SSLtid pthread_self().p
#endif
/* Implements a thread-ID function as requied by openssl */
#if OPENSSL_VERSION_NUMBER < 0x10000000L
static unsigned long
get_thread_id_cb(void)
{
return _SSLtid;
}
#else
static void
get_thread_id_cb(CRYPTO_THREADID *id)
{
CRYPTO_THREADID_set_numeric(id, _SSLtid);
}
#endif
static void
thread_lock_cb(int mode, int which, const char * f, int l)
{
if (which < ssl_num_locks) {
if (mode & CRYPTO_LOCK) {
pthread_mutex_lock(&(ssl_locks[which]));
} else {
pthread_mutex_unlock(&(ssl_locks[which]));
}
}
}
int
init_ssl_locking(void)
{
int i;
ssl_num_locks = CRYPTO_num_locks();
ssl_locks = malloc(ssl_num_locks * sizeof(pthread_mutex_t));
if (ssl_locks == NULL)
return -1;
for (i = 0; i < ssl_num_locks; i++) {
pthread_mutex_init(&(ssl_locks[i]), NULL);
}
#if OPENSSL_VERSION_NUMBER < 0x10000000L
CRYPTO_set_id_callback(get_thread_id_cb);
#else
CRYPTO_THREADID_set_callback(get_thread_id_cb);
#endif
CRYPTO_set_locking_callback(thread_lock_cb);
return 0;
}
原文件地址:https://libevent.org/libevent-book/Ref6_bufferevent.html