libevent之evbuffer

Philosophy發表於2024-06-26

目錄
  • Evbuffers:緩衝 IO 的實用程式功能
    • 簡介
    • 建立或釋放 evbuffer
    • Evbuffers 和執行緒安全
    • 檢查 evbuffer
    • 向 evbuffer 新增資料:基礎知識
    • 將資料從一個 evbuffer 移動到另一個 evbuffer
    • 將資料新增到 evbuffer 的前面
    • 重新排列 evbuffer 的內部佈局
    • 從 evbuffer 中刪除資料
    • 從 evbuffer 中複製資料
    • 面向行的輸入
    • 在 evbuffer 中搜尋
    • 在不復制資料的情況下檢查資料
    • 直接將資料新增到 evbuffer
    • 帶 evbuffers 的網路 IO
    • Evbuffers 和回撥
    • 使用基於 evbuffer 的 IO 避免資料複製
    • 將檔案新增到 evbuffer
    • 對檔案段進行細粒度控制
    • 透過引用將一個 evbuffer 新增到另一個 evbuffer
    • 使 evbuffer 僅能新增或刪除
    • 過時的 evbuffer 函式

Evbuffers:緩衝 IO 的實用程式功能

簡介

Libevent 的 evbuffer 功能實現了一個位元組佇列, 最佳化了將資料新增到末尾並從前面刪除資料。

Evbuffers 通常可用於執行“緩衝區” 緩衝網路 IO 的一部分。它們不提供以下功能 安排 IO 或在 IO 準備就緒時觸發 IO:就是這樣 Buffer事件確實如此。

本章中的函式在 event2/buffer.h 中宣告,除非 另有說明。

建立或釋放 evbuffer

介面

struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);

這些函式應該比較清楚: evbuffer_new() 分配 並返回一個新的空 evbuffer,evbuffer_free() 刪除一個和 它的所有內容。

這些函式從 Libevent 0.8 開始就已經存在了。

Evbuffers 和執行緒安全

介面

int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);

預設情況下,從多個執行緒訪問 evbuffer 是不安全的 。如果需要多個執行緒同時訪問,可以呼叫 evbuffer_enable_locking() 在 evbuffer 上。如果其 lock 引數為 NULL,則 Libevent使用evthread_set_lock_creation_callback提供建立函式分配新鎖 。否則 它使用引數作為鎖。

evbuffer_lock() 和 evbuffer_unlock() 函式獲取和 分別釋放 EVBUFFER 上的鎖。您可以使用它們來 使一組操作成為原子操作。如果尚未啟用鎖定 evbuffer,這些函式不執行任何操作。

(請注意,您不需要呼叫 evbuffer_lock() 和 evbuffer_unlock() 圍繞單個操作:如果鎖定是 在 EVSuBuffer 上啟用,各個操作已經是原子操作。 您只需要手動鎖定 evbuffer 時,您才需要手動鎖定 evbuffer 一個需要在沒有另一個執行緒對接的情況下執行的操作。

這些函式都是在 Libevent 2.0.1-alpha 中引入的。

檢查 evbuffer

介面

size_t evbuffer_get_length(const struct evbuffer *buf);

此函式返回儲存在 evbuffer 中的位元組數。

它是在 Libevent 2.0.1-alpha 中引入的。

介面

size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

此函式返回連續儲存在evbuffer前面的位元組數。evbuffer中的位元組可以儲存在多個單獨的儲存器塊中;此函式返回當前儲存在第一個塊中的位元組數。

它是在 Libevent 2.0.1-alpha 中引入的。

向 evbuffer 新增資料:基礎知識

介面

int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);

此函式將資料中的 datlen 位元組追加到 buf 的末尾。成功時返回 0,失敗時返回 -1。

介面

int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);

這些函式將格式化資料追加到 buf 的末尾。格式 引數和其他剩餘引數的處理方式就好像由 C 處理一樣 庫函式分別為“printf”和“vprintf”。主要工作內容 返回追加的位元組數。

介面

int evbuffer_expand(struct evbuffer *buf, size_t datlen);

此函式更改緩衝區中的最後一個記憶體塊,或新增一個新的記憶體塊,使緩衝區現在足夠大,可以在沒有任何進一步分配的情況下包含datlen位元組。

例子

/* Here are two ways to add "Hello world 2.0.1" to a buffer. */
/* Directly: */
evbuffer_add(buf, "Hello world 2.0.1", 17);

/* Via printf: */
evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);

evbuffer_add() 和 evbuffer_add_printf() 函式是在 Libevent 0.8;evbuffer_expand() 在 Libevent 0.9 中,並且 evbuffer_add_vprintf() 首次出現在 Libevent 1.1 中。

將資料從一個 evbuffer 移動到另一個 evbuffer

為了提高效率,Libevent 最佳化了從 一個 evbuffer 到另一個 evbuffer。

介面

int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);
int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
    size_t datlen);

evbuffer_add_buffer() 函式將 src的所有資料 移動到dst末尾。成功時返回 0,失敗時返回 -1。

evbuffer_remove_buffer() 函式精確地移動 datlen 位元組 從 src到 dst 的末尾,儘可能少地複製。如果 要移動的位元組數少於 datlen,它會移動所有位元組。 它返回移動的位元組數。

我們在 Libevent 0.8 中引入了 evbuffer_add_buffer(); evbuffer_remove_buffer() 是 Libevent 2.0.1-alpha 中的新功能。

將資料新增到 evbuffer 的前面

介面

int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);
int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);

這些函式的行為為 evbuffer_add() 和 evbuffer_add_buffer() 除了它們將資料移動到 目標緩衝區。

這些函式應謹慎使用,切勿在 evbuffer 上使用 與 BufferEvent 共享。它們是 Libevent 2.0.1-alpha 中的新功能。

重新排列 evbuffer 的內部佈局

有時,您希望檢視evbuffer前面的前N個位元組的資料,並將其視為一個連續的位元組陣列。要做到這一點,必須首先確保緩衝區的前面確實是連續的。。

介面

unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);

evbuffer_pullup() 函式將 buf 的第一個大小位元組“線性化”,根據需要複製或移動它們以確保它們都是 連續並佔用相同的記憶體塊。如果大小是 負數,該函式將整個緩衝區線性化。如果大小是 大於緩衝區中的位元組數,函式返回NULL。否則,evbuffer_pullup() 返回指向第一個的指標 BUF 中的位元組。

呼叫大大小的 evbuffer_pullup() 可能會很慢,因為 它可能需要複製整個緩衝區的內容。

#include <event2/buffer.h>
#include <event2/util.h>

#include <string.h>

int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
{
    /* Let's parse the start of a SOCKS4 request!  The format is easy:
     * 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
     * destip. */
    unsigned char *mem;

    mem = evbuffer_pullup(buf, 8);

    if (mem == NULL) {
        /* Not enough data in the buffer */
        return 0;
    } else if (mem[0] != 4 || mem[1] != 1) {
        /* Unrecognized protocol or command */
        return -1;
    } else {
        memcpy(port, mem+2, 2);
        memcpy(addr, mem+4, 4);
        *port = ntohs(*port);
        *addr = ntohl(*addr);
        /* Actually remove the data from the buffer now that we know we
           like it. */
        evbuffer_drain(buf, 8);
        return 1;
    }
}

注意

呼叫 evbuffer_pullup(),其大小等於 返回的值 evbuffer_get_contiguous_space() 不會導致任何資料被 複製或移動。

evbuffer_pullup() 函式是 Libevent 2.0.1-alpha 中的新功能: 以前版本的 Libevent 始終保持 evbuffer 資料連續, 不計成本。

從 evbuffer 中刪除資料

介面

int evbuffer_drain(struct evbuffer *buf, size_t len);
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);

evbuffer_remove() 函式將 buf 前面的第一個 datlen 位元組複製並移動到 data 的記憶體中。如果有 少於 DATLEN 位元組數,該函式將複製所有位元組 有。失敗時返回值為 -1,否則為 複製的位元組數。

evbuffer_drain() 函式的行為為 evbuffer_remove(),除了 它不會複製資料:它只是將其從前面刪除 緩衝區。成功時返回 0,失敗時返回 -1。

Libevent 0.8 引入了 evbuffer_drain();evbuffer_remove() 出現在 libevent 0.9.

從 evbuffer 中複製資料

有時,您希望在緩衝區的開頭獲取資料的副本,而不刪除它。例如,您可能想要檢視完整的記錄某種已經到達,沒有刪除任何資料(如 evbuffer_remove會做的),或在內部重新排列緩衝區(如 evbuffer_pullup() 就可以了。

介面

ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t datlen);
ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf,
     const struct evbuffer_ptr *pos,
     void *data_out, size_t datlen);

evbuffer_copyout() 的行為與 evbuffer_remove() 類似,但不從緩衝區中刪除所有資料。也就是說,它將 buf 前面的第一個 datlen 位元組複製到 data 的記憶體中。如果有 少於 DATLEN 位元組數,該函式將複製所有位元組 有。失敗時返回值為 -1,否則為 複製的位元組數。

evbuffer_copyout_from() 函式的行為類似於 evbuffer_copyout(),但 它不是從緩衝區的前面複製位元組,而是複製它們 從 pos 中提供的位置開始。請參閱“在 evbuffer“,以獲取有關evbuffer_ptr結構的資訊。

如果從緩衝區複製資料的速度太慢,請改用 evbuffer_peek()。

#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <stdlib.h>

int get_record(struct evbuffer *buf, size_t *size_out, char **record_out)
{
    /* Let's assume that we're speaking some protocol where records
       contain a 4-byte size field in network order, followed by that
       number of bytes.  We will return 1 and set the 'out' fields if we
       have a whole record, return 0 if the record isn't here yet, and
       -1 on error.  */
    size_t buffer_len = evbuffer_get_length(buf);
    ev_uint32_t record_len;
    char *record;

    if (buffer_len < 4)
       return 0; /* The size field hasn't arrived. */

   /* We use evbuffer_copyout here so that the size field will stay on
       the buffer for now. */
    evbuffer_copyout(buf, &record_len, 4);
    /* Convert len_buf into host order. */
    record_len = ntohl(record_len);
    if (buffer_len < record_len + 4)
        return 0; /* The record hasn't arrived */

    /* Okay, _now_ we can remove the record. */
    record = malloc(record_len);
    if (record == NULL)
        return -1;

    evbuffer_drain(buf, 4);
    evbuffer_remove(buf, record, record_len);

    *record_out = record;
    *size_out = record_len;
    return 1;
}

evbuffer_copyout() 函式首次出現在 Libevent 2.0.5-alpha 中; evbuffer_copyout_from() 是在 Libevent 2.1.1-alpha 中新增的。

面向行的輸入

介面

enum evbuffer_eol_style {
        EVBUFFER_EOL_ANY,
        EVBUFFER_EOL_CRLF,
        EVBUFFER_EOL_CRLF_STRICT,
        EVBUFFER_EOL_LF,
        EVBUFFER_EOL_NUL
};
char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
    enum evbuffer_eol_style eol_style);

許多 Internet 協議使用基於行的格式。evbuffer_readln() 函式從 evbuffer 的前面提取一行並返回它 在新分配的 NUL終止字串中。如果不是n_read_out NULL,**n_read_out* 設定為字串中的位元組數 返回。如果沒有要讀取的整行,則函式返回 零。行終止符不包含在複製的字串中。

evbuffer_readln() 函式理解 4 種行終止格式:

  • EVBUFFER_EOL_LF

    行尾是單個換行符。(這也是 稱為“\n”。它是 ASCII 值為 0x0A。

  • EVBUFFER_EOL_CRLF_STRICT

    一行的末尾是單回車,後跟一個 單行換行。(這也稱為“\r\n”。ASCII 值 是0x0D 0x0A)。

  • EVBUFFER_EOL_CRLF

    該行的末尾是可選的回車符,後跟 換行。(換言之,它要麼是“\r\n”,要麼是“\n”。 此格式在分析基於文字的 Internet 時很有用 協議,因為標準通常規定“\r\n” 線路終止器,但不合格的客戶有時會說只是 “\n”。

  • EVBUFFER_EOL_ANY

    行尾是任意數量的回車符的任意序列 和換行符。這種格式不是很有用;它 主要是為了向後相容而存在的。

  • EVBUFFER_EOL_NUL

    行尾是值為 0 的單個位元組,即 一個 ASCII NUL。

(請注意,如果您使用 event_set_mem_functions() 來覆蓋 預設 malloc,evbuffer_readln 返回的字串將是 由您指定的 malloc-replacement 分配。

char *request_line;
size_t len;

request_line = evbuffer_readln(buf, &len, EVBUFFER_EOL_CRLF);
if (!request_line) {
    /* The first line has not arrived yet. */
} else {
    if (!strncmp(request_line, "HTTP/1.0 ", 9)) {
        /* HTTP 1.0 detected ... */
    }
    free(request_line);
}

evbuffer_readln() 介面在 Libevent 1.4.14-stable 和 後。EVBUFFER_EOL_NUL是在 Libevent 2.1.1-alpha 中新增的。

在 evbuffer 中搜尋

evbuffer_ptr結構指向 evbuffer 中的一個位置, 幷包含可用於遍歷 EVBUFFER 的資料。

介面

struct evbuffer_ptr {
        ev_ssize_t pos;
        struct {
                /* internal fields */
        } _internal;
};

pos 欄位是唯一的公共欄位;其他的不應該 由使用者程式碼使用。它將 evbuffer 中的位置指示為 從一開始就偏移。

介面

struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer,
    const char *what, size_t len, const struct evbuffer_ptr *start);
struct evbuffer_ptr evbuffer_search_range(struct evbuffer *buffer,
    const char *what, size_t len, const struct evbuffer_ptr *start,
    const struct evbuffer_ptr *end);
struct evbuffer_ptr evbuffer_search_eol(struct evbuffer *buffer,
    struct evbuffer_ptr *start, size_t *eol_len_out,
    enum evbuffer_eol_style eol_style);

evbuffer_search() 函式掃描緩衝區以查詢出現 len-character 字串 what。它返回evbuffer_ptr一個包含 字串的位置,如果未找到字串,則為 -1。如果提供了 start 引數,則它是搜尋的位置 應該開始;否則,搜尋將從字串的開頭開始。

evbuffer_search_range() 函式的行為與evbuffer_search相同,不同之處在於它只考慮what在start和end欄位之間的資料。

evbuffer_search_eol() 類似於evbuffer_readlen,探測行結束符,只是該函式並不複製該行。該函式返回evbuffer_ptr結構,其中的pos指明瞭行結束符的起始地址。如果eol_len_out不是NULL,則其被置為EOL字串的長度。

介面

enum evbuffer_ptr_how {
        EVBUFFER_PTR_SET,
        EVBUFFER_PTR_ADD
};
int evbuffer_ptr_set(struct evbuffer *buffer, struct evbuffer_ptr *pos,
    size_t position, enum evbuffer_ptr_how how);

evbuffer_ptr_set 函式操作 evbuffer_ptr緩衝區的 pos。如果 how是EVBUFFER_PTR_SET, 指標將移動到緩衝區內的絕對位置。 如果how是EVBUFFER_PTR_ADD,指標將向前移動position位元組。此函式在成功時返回 0,在失敗時返回 -1。

#include <event2/buffer.h>
#include <string.h>

/* Count the total occurrences of 'str' in 'buf'. */
int count_instances(struct evbuffer *buf, const char *str)
{
    size_t len = strlen(str);
    int total = 0;
    struct evbuffer_ptr p;

    if (!len)
        /* Don't try to count the occurrences of a 0-length string. */
        return -1;

    evbuffer_ptr_set(buf, &p, 0, EVBUFFER_PTR_SET);

    while (1) {
         p = evbuffer_search(buf, str, len, &p);
         if (p.pos < 0)
             break;
         total++;
         evbuffer_ptr_set(buf, &p, 1, EVBUFFER_PTR_ADD);
    }

    return total;
}

警告

任何修改 evbuffer 或其佈局的呼叫都會使所有未執行的 evbuffer _ ptr 值失效,並使其不安全。

這些介面是 Libevent 2.0.1-alpha 中的新功能。

在不復制資料的情況下檢查資料

有時,您希望在 evbuffer 中讀取資料而不將其複製出來(如 evbuffer_copyout() ),並且沒有重新排列 evbuffer 的內部 記憶體(如 evbuffer_pullup() )。有時您可能希望在 evbuffer 的中間。

您可以透過以下方式執行此操作:

介面

struct evbuffer_iovec {
        void *iov_base;
        size_t iov_len;
};

//返回值未需要的資料塊(evbuffer_iovec)的數量
int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len,
    struct evbuffer_ptr *start_at,
    struct evbuffer_iovec *vec_out, int n_vec);


當你呼叫 evbuffer_peek() 時,你給它一個 evbuffer_iovec 陣列 vec_out結構。陣列的長度為 n_vec。它設定了這些 結構,以便每個結構都包含指向 evbuffer 塊的指標 內部 RAM (iov_base),以及其中設定的記憶體長度 塊。

如果 len 小於 0,則 evbuffer_peek() 嘗試填充所有 您給它evbuffer_iovec結構。否則,它會填充它們,直到 要麼它們都被使用,要麼至少 len 位元組是可見的。如果 函式可以給你所有你要求的資料,它返回 evbuffer_iovec它實際使用的結構。否則,它將返回 它需要的數字才能滿足您的要求。

ptr 為 NULL 時,evbuffer_peek() 從緩衝區的開頭開始。 否則,它從 ptr 中給出的指標開始。

例子

{
    /* Let's look at the first two chunks of buf, and write them to stderr. */
    int n, i;
    struct evbuffer_iovec v[2];
    n = evbuffer_peek(buf, -1, NULL, v, 2);
    for (i=0; i<n; ++i) { /* There might be less than two chunks available. */
        fwrite(v[i].iov_base, 1, v[i].iov_len, stderr);
    }
}

{
    /* Let's send the first 4906 bytes to stdout via write. */
    int n, i, r;
    struct evbuffer_iovec *v;
    size_t written = 0;

    /* determine how many chunks we need. */
    n = evbuffer_peek(buf, 4096, NULL, NULL, 0);
    /* Allocate space for the chunks.  This would be a good time to use
       alloca() if you have it. */
    v = malloc(sizeof(struct evbuffer_iovec)*n);
    /* Actually fill up v. */
    n = evbuffer_peek(buf, 4096, NULL, v, n);
    for (i=0; i<n; ++i) {
        size_t len = v[i].iov_len;
        if (written + len > 4096)
            len = 4096 - written;
        r = write(1 /* stdout */, v[i].iov_base, len);
        if (r<=0)
            break;
        /* We keep track of the bytes written separately; if we don't,
           we may write more than 4096 bytes if the last chunk puts
           us over the limit. */
        written += len;
    }
    free(v);
}

{
    /* Let's get the first 16K of data after the first occurrence of the
       string "start\n", and pass it to a consume() function. */
    struct evbuffer_ptr ptr;
    struct evbuffer_iovec v[1];
    const char s[] = "start\n";
    int n_written;

    ptr = evbuffer_search(buf, s, strlen(s), NULL);
    if (ptr.pos == -1)
        return; /* no start string found. */

    /* Advance the pointer past the start string. */
    if (evbuffer_ptr_set(buf, &ptr, strlen(s), EVBUFFER_PTR_ADD) < 0)
        return; /* off the end of the string. */

    while (n_written < 16*1024) {
        /* Peek at a single chunk. */
        if (evbuffer_peek(buf, -1, &ptr, v, 1) < 1)
            break;
        /* Pass the data to some user-defined consume function */
        consume(v[0].iov_base, v[0].iov_len);
        n_written += v[0].iov_len;

        /* Advance the pointer so we see the next chunk next time. */
        if (evbuffer_ptr_set(buf, &ptr, v[0].iov_len, EVBUFFER_PTR_ADD)<0)
            break;
    }
}

筆記

  • 修改evbuffer_iovec指向的資料可能會導致 未定義的行為。
  • 如果呼叫任何修改 evbuffer 的函式,則指標 evbuffer_peek() 產量可能無效。
  • 如果您的 evbuffer 可以在多個執行緒中使用,請確保鎖定 在呼叫 evbuffer_peek() 之前,先用 evbuffer_lock() 解鎖它 使用 evbuffer_peek() 為您提供的範圍後。

此函式是 Libevent 2.0.2-alpha 中的新功能。

直接將資料新增到 evbuffer

有時您想直接在 evbuffer 中插入資料資訊,而不使用 首先將其寫入字元陣列,然後將其複製到 evbuffer_add()。您可以使用一對高階函式來執行 這:evbuffer_reserve_space() 和 evbuffer_commit_space()。 與 evbuffer_peek() 一樣,這些函式使用 evbuffer_iovec 結構,以提供對 EVbuffer 內部記憶體的直接訪問。

介面

int evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
    struct evbuffer_iovec *vec, int n_vecs);
int evbuffer_commit_space(struct evbuffer *buf,
    struct evbuffer_iovec *vec, int n_vecs);

evbuffer_reserve_space() 函式為您提供指向內部空間的指標 evbuffer。它會根據需要擴充套件緩衝區,以提供最小大小的位元組。指向這些範圍及其長度的指標將是 儲存在您使用 VEC 傳入的向量陣列中;n_vec是 此陣列的長度。

n_vec 的值必須至少為 1。如果您只提供一個 vector,則 Libevent 將確保您擁有所有連續空間 您在單個範圍中請求,但可能需要重新排列 緩衝或浪費記憶體以執行此操作。為了獲得更好的效能, 提供至少 2 個向量。該函式返回提供的數量 它需要的向量,用於您請求的空間。

寫入這些向量的資料不是緩衝區的一部分 直到你呼叫 evbuffer_commit_space(),它實際上使資料 你把計數寫成在緩衝區中。如果要提交更少的空間 比您要求的要少,您可以iov_len減少任何 evbuffer_iovec給你的結構。您也可以減少傳回 向量比你得到的要多。evbuffer_commit_space() 函式 成功時返回 0,失敗時返回 -1。

注意事項

  • 呼叫任何重新排列 evbuffer 或向其新增資料的函式 evbuffer 將使您從中獲得的指標失效 evbuffer_reserve_space()。
  • 在當前的實現中,evbuffer_reserve_space() 從不使用 超過兩個向量,無論使用者提供多少。這可能 在將來的版本中進行更改。
  • 可以安全地呼叫 evbuffer_reserve_space() 任意次數。
  • 如果您的 evbuffer 可以在多個執行緒中使用,請確保鎖定 在呼叫 evbuffer_reserve_space() 之前,它與 evbuffer_lock() 一起使用 提交後解鎖。

/* Suppose we want to fill a buffer with 2048 bytes of output from a
   generate_data() function, without copying. */
struct evbuffer_iovec v[2];
int n, i;
size_t n_to_add = 2048;

/* Reserve 2048 bytes.*/
n = evbuffer_reserve_space(buf, n_to_add, v, 2);
if (n<=0)
   return; /* Unable to reserve the space for some reason. */

for (i=0; i<n && n_to_add > 0; ++i) {
   size_t len = v[i].iov_len;
   if (len > n_to_add) /* Don't write more than n_to_add bytes. */
      len = n_to_add;
   if (generate_data(v[i].iov_base, len) < 0) {
      /* If there was a problem during data generation, we can just stop
         here; no data will be committed to the buffer. */
      return;
   }
   /* Set iov_len to the number of bytes we actually wrote, so we
      don't commit too much. */
   v[i].iov_len = len;
   n_to_add -= len;
}

/* We commit the space here.  Note that we give it 'i' (the number of
   vectors we actually used) rather than 'n' (the number of vectors we
   had available. */
if (evbuffer_commit_space(buf, v, i) < 0)
   return; /* Error committing */

壞例子

/* Here are some mistakes you can make with evbuffer_reserve().
   DO NOT IMITATE THIS CODE. */
struct evbuffer_iovec v[2];

{
  /* Do not use the pointers from evbuffer_reserve_space() after
     calling any functions that modify the buffer. */
  evbuffer_reserve_space(buf, 1024, v, 2);
  evbuffer_add(buf, "X", 1);
  /* WRONG: This next line won't work if evbuffer_add needed to rearrange
     the buffer's contents.  It might even crash your program. Instead,
     you add the data before calling evbuffer_reserve_space. */
  memset(v[0].iov_base, 'Y', v[0].iov_len-1);
  evbuffer_commit_space(buf, v, 1);
}

{
  /* Do not modify the iov_base pointers. */
  const char *data = "Here is some data";
  evbuffer_reserve_space(buf, strlen(data), v, 1);
  /* WRONG: The next line will not do what you want.  Instead, you
     should _copy_ the contents of data into v[0].iov_base. */
  v[0].iov_base = (char*) data;
  v[0].iov_len = strlen(data);
  /* In this case, evbuffer_commit_space might give an error if you're
     lucky */
  evbuffer_commit_space(buf, v, 1);
}

自 Libevent 以來,這些函式一直存在於其現有介面中 2.0.2-阿爾法。

帶 evbuffers 的網路 IO

Libevent 中 evbuffers 最常見的用例是網路 IO。 用於在 evbuffer 上執行網路 IO 的介面為:

介面

int evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd);
int evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
        ev_ssize_t howmuch);
int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch);

evbuffer_read() 函式從 套接字 fdbuffer末尾。它返回讀取的位元組數 成功,EOF 為 0,錯誤為 -1。請注意,該錯誤可能 指示非阻塞操作不會成功;你需要 檢查 EAGAIN(或 Windows 上的 WSAEWOULDBLOCK)的錯誤程式碼。 如果多少是負數,evbuffer_read() 會嘗試猜測多少 讀取自身。

evbuffer_write_atmost() 函式嘗試將buffer 前面howmuch位元組寫入套接字 fd。它返回一個 成功時寫入的位元組數,失敗時寫入的位元組數為 -1。與 evbuffer_read(),您需要檢查錯誤程式碼,看看 error 是真實的,或者只是表示非阻塞 IO 不能 立即完成。如果你給howmuch一個負值, 我們嘗試寫入緩衝區的全部內容。

呼叫 evbuffer_write() 與呼叫帶有負數的 howmuch 引數evbuffer_write_atmost()函式功能相同:它將嘗試儘可能多地重新整理緩衝區。

在 Unix 上,這些函式應該適用於任何檔案描述符 支援讀寫。在 Windows 上,僅支援套接字。

請注意,當您使用 bufferevents 時,您不需要呼叫 這些 IO 功能;BufferEvents 程式碼會為您完成此操作。

evbuffer_write_atmost() 函式是在 Libevent 2.0.1-alpha 中引入的。

Evbuffers 和回撥

evbuffers 的使用者經常想知道何時將資料新增到或 從 evbuffer 中刪除。為了支援這一點,Libevent 提供了一個 通用 EVPuamp 回撥機制。

介面

struct evbuffer_cb_info {
        size_t orig_size;
        size_t n_added;
        size_t n_deleted;
};

typedef void (*evbuffer_cb_func)(struct evbuffer *buffer,
    const struct evbuffer_cb_info *info, void *arg);

每當新增或刪除資料時,都會呼叫 evbuffer 回撥 從 evbuffer 。它接收緩衝區,指向 evbuffer_cb_info結構和使用者提供的引數。這 evbuffer_cb_info結構的 orig_size 欄位記錄多少位元組 在其大小改變之前,緩衝區上有;它n_added領域 記錄向緩衝區新增了多少位元組及其n_deleted 欄位記錄刪除了多少位元組。

介面

struct evbuffer_cb_entry;
struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer,
    evbuffer_cb_func cb, void *cbarg);

evbuffer_add_cb() 函式向 evbuffer 新增回撥,並且 返回一個不透明的指標,稍後可用於引用此指標 特定的回撥例項。cb 引數是 將被呼叫,而 cbarg 是使用者提供的要傳遞的指標 到函式。

您可以在單個 evbuffer 上設定多個回撥。新增 新回撥不會刪除舊回撥。

#include <event2/buffer.h>
#include <stdio.h>
#include <stdlib.h>

/* Here's a callback that remembers how many bytes we have drained in
   total from the buffer, and prints a dot every time we hit a
   megabyte. */
struct total_processed {
    size_t n;
};
void count_megabytes_cb(struct evbuffer *buffer,
    const struct evbuffer_cb_info *info, void *arg)
{
    struct total_processed *tp = arg;
    size_t old_n = tp->n;
    int megabytes, i;
    tp->n += info->n_deleted;
    megabytes = ((tp->n) >> 20) - (old_n >> 20);
    for (i=0; i<megabytes; ++i)
        putc('.', stdout);
}

void operation_with_counted_bytes(void)
{
    struct total_processed *tp = malloc(sizeof(*tp));
    struct evbuffer *buf = evbuffer_new();
    tp->n = 0;
    evbuffer_add_cb(buf, count_megabytes_cb, tp);

    /* Use the evbuffer for a while.  When we're done: */
    evbuffer_free(buf);
    free(tp);
}

順便說一句,釋放非空 evbuffer 不算作 從中耗盡資料,並且釋放 EVBUFFER 不會釋放 使用者為其回撥提供的資料指標。

如果您不希望回撥在緩衝區上永久處於活動狀態,則 可以刪除它(使其永遠消失)或禁用它(將其轉動) 關閉一會兒):

介面

int evbuffer_remove_cb_entry(struct evbuffer *buffer,
    struct evbuffer_cb_entry *ent);
int evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb_func cb,
    void *cbarg);

#define EVBUFFER_CB_ENABLED 1
int evbuffer_cb_set_flags(struct evbuffer *buffer,
                          struct evbuffer_cb_entry *cb,
                          ev_uint32_t flags);
int evbuffer_cb_clear_flags(struct evbuffer *buffer,
                          struct evbuffer_cb_entry *cb,
                          ev_uint32_t flags);

您可以按以下時間收到的evbuffer_cb_entry刪除回撥 您新增了它,或者透過您使用的回撥和指標新增了它。這 evbuffer_remove_cb() 函式在成功時返回 0,在失敗時返回 -1。

evbuffer_cb_set_flags() 函式和 evbuffer_cb_clear_flags() 函式在給定回撥上設定或清除給定的標誌 分別。目前,僅支援一個使用者可見的標誌:EVBUFFER_CB_ENABLED。預設情況下設定該標誌。當它是 清除後,對 evbuffer 的修改不會導致此回撥 被呼叫。

介面

int evbuffer_defer_callbacks(struct evbuffer *buffer, struct event_base *base);

與 bufferevent 回撥一樣,您可以使 evbuffer 回撥不 當 EVBUFFER 發生更改時,立即執行,而是作為給定事件庫的事件迴圈的一部分延遲執行。 如果您有多個 evbuffers 的回撥,這可能會有所幫助 可能導致資料相互新增和刪除,以及 你要避免砸碎堆疊。

如果 evbuffer 的回撥被延遲,那麼當它們最終被推遲時 呼叫時,它們可以彙總多個操作的結果。

與 bufferevents 一樣,evbuffers 在內部進行引用計數,因此 釋放 evbuffer 是安全的,即使它有延遲的回撥 尚未執行。

整個回撥系統在 Libevent 2.0.1-alpha 中是新的。這 evbuffer_cb_(set|clear)_flags() 函式已經存在,它們的 自 2.0.2-alpha 以來的現有介面。

使用基於 evbuffer 的 IO 避免資料複製

真正快速的網路程式設計通常需要儘可能少的資料 儘可能複製。Libevent 提供了一些機制來提供幫助 有了這個。

介面

typedef void (*evbuffer_ref_cleanup_cb)(const void *data,
    size_t datalen, void *extra);

int evbuffer_add_reference(struct evbuffer *outbuf,
    const void *data, size_t datlen,
    evbuffer_ref_cleanup_cb cleanupfn, void *extra);

此函式透過以下方式將一段資料新增到 evbuffer 的末尾 參考。不執行任何複製:相反,evbuffer 只儲存一個 指向儲存在 Data 中的 datlen 位元組的指標。因此, 只要 EVBUFFER 正在使用指標,指標就必須保持有效。 當 evbuffer 不再需要資料時,它會呼叫提供的 “cleanupfn”函式,帶有提供的“data”指標,“datlen”值, 和“額外”指標作為引數。 此函式在成功時返回 0,在失敗時返回 -1。

#include <event2/buffer.h>
#include <stdlib.h>
#include <string.h>

/* In this example, we have a bunch of evbuffers that we want to use to
   spool a one-megabyte resource out to the network.  We do this
   without keeping any more copies of the resource in memory than
   necessary. */

#define HUGE_RESOURCE_SIZE (1024*1024)
struct huge_resource {
    /* We keep a count of the references that exist to this structure,
       so that we know when we can free it. */
    int reference_count;
    char data[HUGE_RESOURCE_SIZE];
};

struct huge_resource *new_resource(void) {
    struct huge_resource *hr = malloc(sizeof(struct huge_resource));
    hr->reference_count = 1;
    /* Here we should fill hr->data with something.  In real life,
       we'd probably load something or do a complex calculation.
       Here, we'll just fill it with EEs. */
    memset(hr->data, 0xEE, sizeof(hr->data));
    return hr;
}

void free_resource(struct huge_resource *hr) {
    --hr->reference_count;
    if (hr->reference_count == 0)
        free(hr);
}

static void cleanup(const void *data, size_t len, void *arg) {
    free_resource(arg);
}

/* This is the function that actually adds the resource to the
   buffer. */
void spool_resource_to_evbuffer(struct evbuffer *buf,
    struct huge_resource *hr)
{
    ++hr->reference_count;
    evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE,
        cleanup, hr);
}

evbuffer_add_reference() 函式已存在 介面從 2.0.2-alpha 開始。

將檔案新增到 evbuffer

某些作業系統提供了將檔案寫入網路的方法 而無需將資料複製到使用者空間。您可以訪問這些 機制(如果可用),具有簡單的介面:

介面

int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
    size_t length);

evbuffer_add_file() 函式假定它有一個開啟的檔案 描述符(不是套接字,這一次!可用於的 FD 讀數。它將檔案的長度位元組(從位置偏移量開始)新增到輸出末尾。成功時返回 0,或返回 -1 失敗。

警告

在 Libevent 2.0.x 中,唯一可靠的資料 新增這種方式是用 evbuffer_write() 將其傳送到網路, 用 evbuffer_drain() 排空它,或用 evbuffer__buffer()。您無法可靠地從緩衝區中提取它 用 evbuffer_remove(),用 evbuffer_pullup() 線性化,依此類推 上。Libevent 2.1.x 嘗試修復此限制。

如果您的作業系統支援 splice() 或 sendfile(),則 Libevent 呼叫時會使用它直接將資料從FD傳送到網路 evbuffer_write(),則完全沒有將資料複製到使用者RAM。如果 splice/sendfile 不存在,但你有 mmap(),Libevent 會 mmap 檔案和你的核心有望發現它永遠不需要 將資料複製到使用者空間。否則,Libevent 將只讀取 資料從磁碟到RAM。

從 evbuffer,或者當 evbuffer 被釋放時。如果這不是你想要的,或者如果 您想要對檔案進行更精細的控制,請參閱file_segment 功能如下。

此函式是在 Libevent 2.0.1-alpha 中引入的。

對檔案段進行細粒度控制

evbuffer_add_file() 介面對於新增相同的檔案效率低下 不止一次,因為它擁有檔案的所有權。

介面

struct evbuffer_file_segment;

struct evbuffer_file_segment *evbuffer_file_segment_new(
        int fd, ev_off_t offset, ev_off_t length, unsigned flags);
void evbuffer_file_segment_free(struct evbuffer_file_segment *seg);
int evbuffer_add_file_segment(struct evbuffer *buf,
    struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length);

evbuffer_file_segment_new() 函式建立並返回一個新的 evbuffer_file_segment 物件來表示基礎檔案的一部分 儲存在從偏移量開始幷包含長度位元組的 FD 中。上 錯誤,則返回 NULL。

檔案段使用 sendfile、splice、mmap、CreateFileMapping、 或 malloc()-and-read(),視情況而定。它們是使用最多的 輕量級支撐機構,並過渡到重量較重的機構 根據需要。(例如,如果您的作業系統支援 sendfile 和 mmap,則檔案 segment 只能使用 sendfile 實現,直到您嘗試實際 檢查其內容。在這一點上,它需要 mmap()ed.)您可以 使用以下標誌控制檔案段的細粒度行為:

  • EVBUF_FS_CLOSE_ON_FREE

    如果設定了此標誌,則釋放檔案段 evbuffer_file_segment_free() 將關閉基礎檔案。

  • EVBUF_FS_DISABLE_MMAP

    如果設定了此標誌,則file_segment將永遠不會使用對映記憶體 style 後端 (CreateFileMapping, mmap) 來表示,即使這樣 要適當。

  • EVBUF_FS_DISABLE_SENDFILE

    如果設定了此標誌,則file_segment將永遠不會使用 sendfile 樣式 此檔案的後端(sendfile、splice),即使會 要適當。

  • EVBUF_FS_DISABLE_LOCKING

    如果設定了此標誌,則不會為檔案段分配任何鎖:它 以任何可以被多人看到的方式使用它都是不安全的 執行緒。

一旦你有了evbuffer_file_segment,你可以將部分或全部新增到一個 evbuffer 使用 evbuffer_add_file_segment()。此處的 offset 引數是指檔案段內的偏移量,而不是偏移量 在檔案本身中。

當您不想再使用檔案段時,可以使用 evbuffer_file_segment_free()。實際儲存不會釋放,直到沒有 evbuffer 不再包含對檔案段片段的引用。

介面

typedef void (*evbuffer_file_segment_cleanup_cb)(
    struct evbuffer_file_segment const *seg, int flags, void *arg);

void evbuffer_file_segment_add_cleanup_cb(struct evbuffer_file_segment *seg,
        evbuffer_file_segment_cleanup_cb cb, void *arg);

您可以將回撥函式新增到將在以下情況下呼叫的檔案段 對檔案段的最終引用已釋出,並且檔案 段即將被釋放。此回撥不得嘗試恢復 檔案段,將其新增到任何緩衝區,依此類推。

這些檔案段函式最早出現在 Libevent 2.1.1-alpha 中; evbuffer_file_segment_add_cleanup_cb() 是在 2.1.2-alpha 中新增的。

透過引用將一個 evbuffer 新增到另一個 evbuffer

您還可以透過引用將一個 evbuffer 新增到另一個 evbuffer:而不是刪除 一個緩衝區的內容並將它們新增到另一個緩衝區,您得到一個 evbuffer 對另一個的引用,它的行為就好像你已經複製了所有的 位元組。

介面

int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
    struct evbuffer *inbuf);

evbuffer_add_buffer_reference() 函式的行為就像您複製了一樣 從 outbufinbuf 的所有資料,但不執行任何不必要的操作 副本。如果成功,則返回 0,失敗時返回 -1。

請注意,對 inbuf 內容的後續更改不會反映在 outbuf 中:此函式透過以下方式新增 evbuffer 的當前內容 引用,而不是 EVbuffer 本身。

另請注意,您不能巢狀緩衝區引用:已 作為一個evbuffer_add_buffer_reference的輸出,不能成為另一個呼叫的輸出

此函式是在 Libevent 2.1.1-alpha 中引入的。

使 evbuffer 僅能新增或刪除

介面

int evbuffer_freeze(struct evbuffer *buf, int at_front);
int evbuffer_unfreeze(struct evbuffer *buf, int at_front);

您可以使用這些函式暫時禁用對 evbuffer 頭部和尾部的修改。bufferevent 程式碼使用它們 內部以防止意外修改前部 輸出緩衝區或輸入緩衝區的末尾。

evbuffer_freeze() 函式是在 Libevent 中引入的 2.0.1-阿爾法。

過時的 evbuffer 函式

evbuffer 介面在 Libevent 2.0 中發生了很大變化。在此之前, 每個 evbuffers 都作為連續的 RAM 塊實現,它 使訪問效率非常低下。

event.h 標頭用於公開 struct evbuffer 的內部結構。 這些不再可用;他們在 1.4 和 2.0 適用於依賴它們工作的任何程式碼。

要訪問 evbuffer 中的位元組數,有一個 EVBUFFER_LENGTH() 宏。實際資料可透過 EVBUFFER_DATA()。這些都可以在 event2/buffer_compat.h 中使用。 但要注意:EVBUFFER_DATA(b) 是 evbuffer_pullup(b, -1),這可能非常昂貴。

其他一些已棄用的介面包括:

已棄用的介面

char *evbuffer_readline(struct evbuffer *buffer);
unsigned char *evbuffer_find(struct evbuffer *buffer,
    const unsigned char *what, size_t len);

evbuffer_readline() 函式的工作方式與當前函式類似 evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY)。

evbuffer_find() 函式將搜尋第一次出現的 緩衝區中的字串,並返回指向它的指標。與 evbuffer_search(),它只能找到第一個字串。留下來 與使用此函式的舊程式碼相容,它現線上性化 整個緩衝區,直到找到的字串的末尾。

回撥介面也不同:

已棄用的介面

typedef void (*evbuffer_cb)(struct evbuffer *buffer,
    size_t old_len, size_t new_len, void *arg);
void evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg);

一個 evbuffer 一次只能設定一個回撥,因此設定一個 new callback 將禁用之前的回撥,並將 NULL 的回撥是禁用回撥的首選方法。

該函式不是獲得evbuffer_cb_info_structure,而是 使用 evbuffer 的舊長度和新長度呼叫。因此,如果old_len 大於 new_len,資料被耗盡。如果new_len更大 比old_len,新增了資料。無法推遲迴調, 因此,新增和刪除從未被批處理到單個回撥中 呼叫。

此處的過時函式在 event2/buffer_compat.h 中仍然可用。

源文件地址:https://libevent.org/libevent-book/Ref7_evbuffer.html

相關文章