libevent之bufferevents

Philosophy發表於2024-06-26

目錄
  • 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);

baseevent_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_filteroutput_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_ratewrite_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

相關文章