1. 程式人生 > 實用技巧 >libevent簡介[翻譯]11 Evbuffers:緩衝IO的功能函式

libevent簡介[翻譯]11 Evbuffers:緩衝IO的功能函式

http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

Libevent的evbuffer函式實現了一個位元組佇列,用來優化從尾部增加資料從頭部刪除資料的效能。

Evbuffers經常用作網路IO緩衝。你不用提供函式管理IO或是在IO準備好的時候觸發,這就是bufferevents所做的事。

建立和釋放evbuffer

介面

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

這個函式非常簡單清晰了,evbuffer_new()

申請建立並返回一個空的evbuffer,evbuffer_free()刪除裡面所有的內容。

Evbuffers和執行緒安全

介面

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

預設情況下,多執行緒同時訪問evbuffer是不安全的,可以在evbuffer上呼叫evbuffer_enable_locking()。如果lock引數是NULL,libevent會申請一個新的lock,並且用這個lock建立一個函式,提供給evthread_set_lock_creation_callback。如果lock不是NULL,那就是用傳遞的lock。

evbuffer_lock()evbuffer_unlock()用來請求和釋放evbuffer上的鎖。你可以使用它們保證操作的原子性。如果沒有lock,函式不會有任何作用。

注意,你不需要在每個操作都呼叫evbuffer_lock()evbuffer_unlock(),因為如果evbuffer已經鎖住了,每個操作都是原子的。你只需在有多個操作需要執行,但是不希望其他執行緒進入的時候加鎖。

檢查evbuffer

介面

size_t evbuffer_get_length(const struct evbuffer *buf);

這個函式返回在evbuffer中的位元組數

介面

size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

這個函式返回放在evbuffer前面連續的位元組數。放在evbuffer的資料是被分割到多個塊的記憶體,這個是返回第一個塊的資料。

向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的後面附加格式化的資料。

介面

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

這個函式修改最後一個塊的記憶體,或是增加一個新的塊,這樣buffer就有足夠大的空間而不用重新申請了。

示例

/* 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移動到另一個

為了效率,libevent已經又花了移動資料的函式

介面

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

evbuffer_remove_buffer()移動最多datlen的資料從dst到src。

在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()類似,只不過是把資料放到buffer前面。

重新排列evbuffer內部的層

有時候你想看evbuffer前面的N個位元組的資料,把它看做一個連續的整體。如果你要這麼做,你必須保證前N個位元組的資料是連續的。

介面

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

evbuffer_pullup()函式線性化buf第一個空間的位元組,拷貝或是移動他們,確保他們都是在同一個塊的連續記憶體。如果空間大於buffer,就返回NULL,否則返回第一個位元組的地址。

如果資料很多,呼叫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()如果size的大小等於evbuffer_get_contiguous_space()的返回值將不會拷貝或移動任何資料。

從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的位元組並且拷貝到記憶體。如果比datlen的長度小,那就全部拷貝。出錯返回-1,正常返回拷貝的位元組數。

evbuffer_drain()evbuffer_remove()行為一樣,但是,它不拷貝資料:僅僅是從buffer前面刪除資料。

從evbuffer拷貝資料出來

有時候你想從buffer前面拷貝資料出來並且不想刪除它。比如,你想看看某些資料是否達到,而不想刪除資料,或者內部重新排列資料。

介面

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()類似,但是不從buffer刪除資料。也就是把錢datlen的資料從buf前面拷貝到記憶體。如果資料比datlen少,就拷貝全部的,出錯返回-1,成功返回拷貝的位元組數。

evbuffer_copyout_from()的行為和evbuffer_copyout()類似,但是不是從buffer的前面拷貝位元組,二是從pos指定的位置拷貝位元組。

如果拷貝資料太慢,可以使用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;
}

線性輸入

介面

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

很多底層協議使用線性的格式。evbuffer_readln()從evbuffer的前面提取一行資料,然後返回一個新建立的NUL結尾的字串。如果n_read_out不是NULL,*n_read_out就指向了字串返回的長度。如果不是整行被讀取,那麼就返回NULL,行結束符並不在拷貝的字串內。

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返回,將會通過你自己定義的申請函式替換

示例

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中查詢

evbuffer_ptr結構體指向了evbuffer申請的空間,包含你可以遍歷evbuffer的資料。

介面

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

pos是唯一公共的欄位。另一個不能在你程式碼中使用。它指向一個evbuffer從開始到offset的位置

介面

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()函式掃描buffer中的內容,查詢到匹配what中字串的位置。返回evbuffer_ptr指向字串的位置,如果沒找到,返回-1.如果start引數提供了,就指定了在查詢開始的位置,否則,從字串開始查詢。

evbuffer_search_range()evbuffer_search行為類似,只是值查詢end之前的資料。

evbuffer_search_eol()函式發現行結束的資料,與evbuffer_readln()類似,只不過不拷貝資料,而是返回這一行資料的開頭位置。如果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操作pos位置的資料。如果how是EVBUFFER_PTR_SET,指標就指向buffer中一個絕對的位置;如果是EVBUFFER_PTR_ADD,就向前移動position位元組。

示例

#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或是layout的呼叫都會是evbuffer_ptr無效,並且不能安全使用。

不拷貝資料來檢查資料

有時候你想讀取evbuffer中的資料,而不拷貝它,不重新排版它。有時候你想檢視evbuffer中間的資料。

你可以這樣做:

介面

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

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在記憶體中(iov_base)的塊,並且長度也設定在哪個塊中。

如果len比0小,evbuffer_peek()就會試著填滿所有的evbuffer_iovec結構體。否則,它會不斷的填滿它們,知道所有的都被使用了,或者len的位元組是可見的。如果函式可以給你所有的你請求的資料,它會返回使用的evbuffer_iovec結構體的數量。否則,返回可能需要的數量。

如果ptr是NULL,evbuffer_peek()從buffer第一個位置開始,否則,從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新增資料

有時候你想直接想evbuffer新增資料,而不是先寫入到一個字串陣列,然後再通過evbuffer_add()新增到裡面。有一對函式可以做到這個:`evbuffer_reserve_space()`和`evbuffer_commit_space()`。和`evbuffer_peek()`一樣,這兩個函式使用evbuffer_iovec結構體來直接訪問記憶體中的evbuffer。

**介面**

```cpp
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內部空間的指標。它把buffer擴充到至少能滿足你提供的size大小。這個指標指向擴充的位置,並且他的長度,會被儲存到你傳入的vec vectores陣列中。n_vec是陣列的長度

n_vec至少是1.如果只提供了一個vector,libevent將會保證你有一個連續的空間,但是可能會重新分配buffer或是浪費記憶體。為了效能,最好提供2個vecotre。函式返回你請求的vector使用的個數。

你寫入到vector中的資料並不會稱為buffer的一元,需要呼叫evbuffer_commit_space(),使得資料寫入到buffer。如果你想提交比你少的資料,你可以減少iov_len。你可以同樣傳入比你提供少的vector。

注意和說明

  • 呼叫函式重新排列evbufer或是新增資料會是通過evbuffer_reserve_space()獲取的指標失效

  • 當前的實現,evbuffer_reserve_space()不會使用超過兩個vector,不管你提供了多少,這個會在未來解決

  • 任何時候呼叫evbuffer_reserve_space()都是安全的

  • 如果你的evbuffer在多執行緒中使用,確保呼叫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;
}

/* 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);
}

使用evbuffer的網路IO

最常見的使用evbuffer的場景就是網路IO。操作網路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()函式從fd中讀取howmuch的位元組資料到buffer中。如果成功,返回讀了幾個位元組,附帶0在遇到EOF的時候,-1表示失敗。注意,error將會指出非阻塞操作沒有正常執行;需要通過EAGAIN或是在Windows上的WSAEWOULDBLOCK來檢查。如果howmuch是負數,evbuffer_read()試著讀取更多的資料。

evbuffer_write_atmost()試著從buffer前面寫入到fd的howmuch位元組。如果成功,返回寫入了多少位元組,-1表示失敗。和evbuffer_read()類似,需要檢測error code來判斷是否真的出錯了,還是表明非阻塞IO僅僅沒完成。如果傳入負數到howmuch,將會盡量寫入更多的資料。

呼叫evbuffer_write()evbuffer_write_atmost()類似,傳入負數到howmuch,將會試著寫入更多的資料。

在Unix上,可以支援read和write呼叫中任意檔案秒速符,在Windows上,只能支援socket。

記住,當使用bufferevent,你不需要呼叫它的IO函式,bufferevent會自己這樣做

Evbuffers和回撥函式

有時候,使用evbuffer的使用者想知道什麼時候添加了資料什麼時候從evbuffer刪除了資料。為了支援這個,libevent提供了基本的evbuffer回撥機制。

介面

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新增或是刪除資料的時候回撥。它接收buffer,一個指向evbuffer_cb_info結構體的指標,和一個使用者自定義引數列表。evbuffer_cb_info結構體的orig_size欄位記錄了在它的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並不會釋放使用者提供的資料指標指向的內容

如果你不想是buffer的回撥函式一直可用,你可以刪除它或是禁止它。

介面

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_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變化的時候不立馬執行,但是也僅僅是在一個事件loop內延遲。如果你有很多evbuffer的回撥導致資料從一個到另一個的新增或是刪除,你可以避免堆疊碎片。

當一個evbuffer的回撥被延遲後,當他們最後被回撥的時候,他們會同一總結多個操作。

和bufferevent一樣,evbuffer也是用引用計數,所以及時有延遲的回撥還滅有執行,釋放evbuffer也是安全的。

避免使用基於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後面新增一塊資料。沒有資料拷貝,轉而替換為把指向的detlen長度的資料指標直接儲存到evbuffer。所以指標必須包含evbuffer可以使用的足夠長的資料。當evbuffer不需要資料時,呼叫cleanupfn函式,傳入資料指標,資料長度和額外的引數驚醒釋放。

示例

#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新增一個檔案

有些系統提供了通過網路寫檔案,避免了使用者層的拷貝。如果可行,你可以通過一些簡單的介面使用這種機制:

介面

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

evbuffer_add_file()函式假設有一個開啟的檔案描述符fd,不是socket,可以讀。在offset的位置增加length的位元組,結束在output。

警告

在Libevent 2.0.x中,唯一一個依賴的事情,通過這種方式做資料新增,就是使用evbuffer_write*()傳送到網路,通過evbuffer_drain()把資料傳輸完成,或者通過evbuffer_*_buffer()移動到另一個evbuffer。你不能可靠的通過evbuffer_remove()從buffer中提取出來,並且通過evbuffer_pullup()線性化,等等。Libevent 2.1.x將會試著去修復這個限制。

如果你的系統支援splice()sendfile(),libevent會使通過evbuffer_write()用它把資料從fd傳送到網路,不需要把資料先拷貝到RAM。如果splice/sendfile不存在,但是有mmap(),Libevent會mmap檔案,你的核心可以希望的發現不需要把資料拷貝到使用者層。否則,libevent會把資料從硬碟拷貝到RAM。

當資料從evbuffer刷新干淨或者evbuffer已經被釋放,檔案描述符就會被關閉。如果你不想這樣做,或者你想細粒度的控制你的檔案,可以參考file_segment。

細粒度控制檔案

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()建立並返回一個用來替換底層在fd中的從offset開始包含length位元組的檔案的新的evbuffer_file_segment結構體。出錯返回NULL。

檔案片段通過sendfile, splice, mmap, CreateFileMapping, 或者 malloc()-and-read()實現。他們建立使用最輕量級的機制,如果需要轉換到重量級的機制。比如,如果系統支援sendfile和mmap,檔案片段就是用sendfile,直到你試著檢查它的內容。這個時候,你需要mmap()。你可以通過這些標識來控制檔案片段的粒度。

  • EVBUF_FS_CLOSE_ON_FREE

evbuffer_file_segment_free()釋放檔案片段後會關閉底層檔案。

  • EVBUF_FS_DISABLE_MMAP

禁止使用記憶體拷貝機制。

  • EVBUF_FS_DISABLE_SENDFILE

禁止使用sendfile機制

  • EVBUF_FS_DISABLE_LOCKING

在使用檔案片段的時候不會加鎖

如果你有evbuffer_file_segment,你可以通過evbuffer_add_file_segment()把一個或是多個新增到evbuffer中。offset在這裡指向檔案片段的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);

你可以為檔案片段增加回調函式,當最後的檔案片段被釋放的時候,會被呼叫。這個回撥不能還原檔案片段或是把它新增到任何緩衝區中。

把evbuffer新增到另一個引用

你可以把一個evbuffer新增到另一個引用,而避免把內容從一個buffer移動到另一個。

介面

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

evbuffer_add_buffer_reference()行為就相當於你把資料從outbuf拷貝到inbuf,但是並沒有任何底層拷貝。

注意,inbuf中內容的修改並不會反映給outbuf,因為你是把內容新增進去,而不是evbuffer自己。

注意,你不能巢狀buffer的索引。

是evbuffer只能新增或是刪除

介面

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

你可以用這兩個函式臨時禁用evbuffer前端或是後端的修改。bufferevent底層的程式碼會使用它們來禁止前端output buffer或是後端input buffer的修改。