Libevent:9Evbuffers緩存IO的實用功能

        Libevent的evbuffer功能實現了一個字節隊列,優化了在隊列尾端增長數據,以及從隊列前端刪除數據的操做html

        Evbuffer用來實現緩存網絡IO中的緩存部分。它們不能用來在條件發生時調度IO或者觸發IO:這是bufferevent作的事情。前端

        本章介紹的函數,除了特別註明的,都是在文件「event2/buffer.h」中聲明。後端

 

一:建立或者釋放evbuffer數組

struct  evbuffer  *evbuffer_new(void);緩存

void  evbuffer_free(struct  evbuffer *buf);安全

        evbuffer_new分配而且返回一個新的空evbuffer,evbuffer_free刪除evbuffer及其全部內容。網絡

 

二:evbuffer和線程安全多線程

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

void  evbuffer_lock(struct  evbuffer  *buf);less

void  evbuffer_unlock(struct  evbuffer  *buf);

        默認狀況下,同一時間在多個線程中訪問evbuffer是不安全的。若是須要多線程使用evbuffer,能夠在evbuffer上調用evbuffer_enable_locking函數。若是lock參數爲NULL,則Libevent使用提供給               evthread_set_lock_creation_callback的鎖建立函數來分配一個新鎖。不然,使用該參數做爲新鎖。

        evbuffer_lock() 和evbuffer_unlock()函數在evbuffer上進行加鎖和解鎖。可使用它們將一些操做原子化。若是evbuffer上沒有使能鎖機制,則這些函數不作任何事。

        注意:不須要在單個操做上調用evbuffer_lock() evbuffer_unlock():若是evbuffer上使能了鎖機制,單個操做已是原子性的了。只在有多餘一個操做須要執行,且不但願其餘線程打斷時,才須要手動鎖住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中的第一個內存塊中的字節數

 

四:向evbuffer中添加數據:基礎

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

        該函數向buf的末尾添加緩衝區data中的datlen個字節。該函數成功時返回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中添加格式化的數據。format參數以及後續的其餘參數相似於C庫中的printf和vprintf函數中的參數。這些函數返回添加到buf中的字節數。

 

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

        擴展evbuffer的可用空間,該函數會改變buffer中的最後一個內存塊,或者添加一個新的內存塊,buffer的可用空間擴展到至少datlen個字節。擴展buffer到足夠大,從而在不須要進行更多的分配狀況下就能容納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之間的數據移動

        基於性能的考慮,Libevent優化了在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函數將src中的datlen個字節的數據移動到dst的末尾,而且儘可能少的複製。若是數據量少於datlen,則移動全部數據。該函數返回移動的數據量。

 

六:向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的前端。

        這些函數要當心使用,不能用在由bufferevent共享的evbuffer上。

 

七:從新安排evbuffer的內部佈局

        有時候但願可以查看(但不取出)evbuffer前端的前N個字節,並將其視爲一個連續存儲的數組。爲此,必須首先保證buffer的前端確實是連續的。

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

        evbuffer_pullup函數將buf的最前面的size個字節「線性化」,經過對其複製或者移動,保證這些字節的連續性而且都存儲在同一個內存塊中。若是size是個負數,則該函數會將整個buffer進行線性化。若是size大於buffer中的字節數,則該函數返回NULL,不然,該函數返回指向第一個字節的指針。

        以一個較大的size調用evbuffer_pullup會很慢,由於可能須要複製整個buffer的內容。

#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 SOCKS4request!  The format is easy:

     * 1 byte of version, 1 byte of command, 2bytes 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 thebuffer now that we know we

           like it. */

        evbuffer_drain(buf, 8);

        return 1;

    }

}

        注意,使用evbuffer_get_contiguous_space的返回值做爲size,調用evbuffer_pullup,則不會引發任何字節的複製或者移動

 

八:從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。若是buf中的字節數少於datlen個,則該函數會複製全部的字節。該函數失敗返回-1,不然返回複製的字節數。

        evbuffer_drain()函數相似於evbuffer_remove,不一樣的是它不復制任何數據,而只是刪除buffer前端的數據。該函數成功返回0,失敗返回-1。

        

九:複製evbuffer中的數據

        有時但願只是複製buffer前端的數據而不刪除它。好比,你可能但願確認某種類型的完整記錄是否已經到達,而不刪除任何數據(就像evbuffer_remove的動做),也不對buffer內部作任何從新部署(就像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,可是並不刪除buffer中的任何數據。也就是說,它只是複製buf前端的前datlen個字節到data。若是buffer中少於datlen個字節,則該函數複製全部存在的字節。該函數失敗時返回-1,成功時返回複製的字節數。

 

        evbuffer_copyout_from函數相似於evbuffer_copyout,但它不是複製buffer前端的數據,而是以pos指明的位置爲起點進行復制。參考「在evbuffer中搜索」一節,查看evbuffer_ptr結構的更多信息。

        若是從buffer中複製數據太慢,則可使用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 someprotocol where records

       containa 4-byte size field in network order, followed by that

       number of bytes.  We will return 1 and set the 'out' fields ifwe

       have a whole record, return 0 if the recordisn'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'tarrived. */

 

   /* We use evbuffer_copyout here so that thesize 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的前端取出一行,而且將其複製返回到一個新的以NULL爲結尾的字符串中。若是n_read_out非空,則將*n_read_out置爲返回字符串的長度。若是buffer中沒有可讀的一整行,則該函數返回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」)。這種格式在解析基於文本的互聯網協議中是頗有用的,由於通常而言,協議標準都規定以」\r\n」做爲行結束符,可是不符合標準的客戶端有時候會使用」\n」。

        EVBUFFER_EOL_ANY:行的結尾是任何順序任何數量的回車符和換行符。這種格式不太經常使用,它的存在只是爲了向後兼容性。

        EVBUFFER_EOL_NUL:行結束符是單個的0字節,也就是ASCII中的NULL字節。

        注意,若是使用了event_set_mem_functions函數替代默認的malloc,則函數evbuffer_readln 返回的字符串將由指定的malloc替代函數進行分配。

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成員是惟一公開的成員;其餘成員不能在用戶代碼中使用。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函數在buffer中掃描長度爲len的what字符串的位置。若是能找到該字符串,則返回的evbuffer_ptr結構中的pos指明該字符串的位置,不然pos爲-1。若是提供了start參數,則該參數指定開始搜索的位置;若是沒有提供,則代表從從buffer的開頭開始搜索。

        evbuffer_search_range函數相似於evbuffer_search,但它只在buffer中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結構posbuffer中的某個位置。若是how爲EVBUFFER_PTR_SET,則pos移動到buffer中的position位置,若是是EVBUFFER_PTR_ADD,pointer向後移動position個字節。所以,若是pos沒有初始化的話,則how參數只能爲EVBUFFER_PTR_SET。該函數成功時返回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 occurrencesof 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的值失效,使用失效的evbuffer_ptr是不安全的

 

十二:檢查數據而不進行復制

        有時但願在不復製出數據的狀況下讀取evbuffer中的數據,並且不對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函數時,在vec_out中給定一個evbuffer_iovec結構的數組。數組長度爲n_vec。該函數設置數組中的結構體,使每一個結構體的iov_base都指向evbuffer內部的一個內存塊,而且將iov_len置爲內存塊的長度。

        若是len小於0,則evbuffer_peek函數會盡量的設置全部給定的evbuffer_iovec結構。不然,要麼至少填充len個字節到evbuffer_iovec中,要麼將全部evbuffer_iovec都填充滿。若是該函數可以獲得全部請求的字節,則該函數將返回實際使用的evbuffer_iovec結構的個數,不然,它返回爲了能獲得全部數據而須要的evbuffer_iovec的個數。

        若是ptr爲NULL,則evbuffer_peek從buffer的起始處開始取數據,不然,從start_at參數開始取數據。

{

    /* Let's look at the first two chunks of buf, and writethem 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 chunksavailable. */

        fwrite(v[i].iov_base,  1,  v[i].iov_len, stderr);

    }

}

 

{

    /* Let's send the first 4906 bytes tostdout 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 writtenseparately; if we don't,

           we may write more than 4096 bytes ifthe 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 itto 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-definedconsume function */

        consume(v[0].iov_base,  v[0].iov_len);

        n_written  +=  v[0].iov_len;

 

        /* Advance the pointer so we see thenext 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返回的內存塊,則須要解鎖

 

十三:直接向evbuffer中添加數據

        若是須要向evbuffer中直接插入數據,而不是像evbuffer_add那樣,先寫入一個字符數組,而後再將字符數組複製到evbuffer中。可使用下面的函數: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函數擴充buf的最後一個內存塊的空間,返回evbuffer內部空間的指針。它會將buffer的可用空間擴充到至少size個字節;指向這些擴充的內存塊的指針以及內存塊長度將會存儲在vec結構中,n_vec指明瞭該數組的長度。

        n_vec至少爲1,若是隻給定了一個vector,則Libevent會保證將全部請求的連續空間填充到一個內存塊中,可是這樣會對buffer從新佈局,或者會浪費內存。若是想要更好的性能,則至少應該提供2個vector,該函數返回請求空間須要的vector個數。

        寫入到這些vector中的數據,直到調用evbuffer_commit_space以前,都不算是buffer的一部分,該函數會使vector中的數據成爲buffer中的數據。若是但願提交少於請求的數據,能夠減小任一evbuffer_iovec結構體的iov_len,或者還能夠傳遞較少的vector。evbuffer_commit_space函數成功時返回0,失敗時返回-1。

 

        注意:調用任何從新佈局evbuffer的函數,或者向evbuffer添加數據,會使得從evbuffer_reserve_space返回的指針變得無效。

        在當前的實現中,evbuffer_reserve_space不會使用多餘兩個的vector,而無論用戶提供了多少,或許會在將來版本中有所改變。

        調用屢次evbuffer_reserve_space是安全的。

        若是evbuffer在多線程中使用,則在調用evbuffer_reserve_space函數以前應該使用evbuffer_lock函數進行加鎖,而且一旦commit會後就解鎖。

/*Suppose  we  want to fill a buffer with 2048 bytes ofoutput 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 forsome 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 morethan n_to_add bytes. */

      len = n_to_add;

   if (generate_data(v[i].iov_base,  len) < 0) {

      /* If there was a problem during datageneration, we can just stop

         here; no data will be committed to thebuffer. */

      return;

   }

   /* Set iov_len to the number of bytes weactually wrote, so we

      don't commit too much. */

   v[i].iov_len = len;

}

 

/* We commit the spacehere.  Note that we give it 'i' (thenumber 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 */

Bad Examples

/* Here are some mistakes youcan make with evbuffer_reserve().

   DO NOT IMITATE THIS CODE. */

struct evbuffer_iovec v[2];

 

{

  /*Do not use thepointers from evbuffer_reserve_space() after

     calling any functions that modify thebuffer.*/

  evbuffer_reserve_space(buf,  1024,  v, 2);

  evbuffer_add(buf, "X", 1);

  /* WRONG: This next line won't work ifevbuffer_add needed to rearrange

     the buffer's contents.  It might even crash your program.Instead,

     you add the data before callingevbuffer_reserve_space. */

  memset(v[0].iov_base, 'Y', v[0].iov_len-1);

  evbuffer_commit_space(buf, v, 1);

}

 

{

  /*Do not modify theiov_base pointers. */

  const char *data = "Here is somedata";

  evbuffer_reserve_space(buf, strlen(data), v,1);

  /* WRONG: The next line will not do what youwant.  Instead, you

    should _copy_ thecontents 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 mightgive an error if you're

     lucky */

  evbuffer_commit_space(buf, v, 1);

}

 

十四:用evbuffer進行網絡IO

        Libevent中evbuffer最多見的用途是用來進行網絡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函數從fd中讀取howmuch個字節buffer的尾端。他返回成功讀取的字節數,遇到EOF時返回0,發生錯誤返回-1。注意發生錯誤有可能代表一個非阻塞的操做失敗了;能夠經過檢查錯誤碼是否爲EAGAIN(在Windows上爲WSAEWOULDBLOCK )來肯定。若是howmuch參數爲負數,則evbuffer_read本身決定讀取多少字節。

        evbuffer_write_atmost函數嘗試從buffer的前端發送howmuch字節到fd。該函數返回成功寫入的字節數,失敗時返回-1。與evbuffer_read相似,須要檢查錯誤碼肯定是否確實發生了錯誤,仍是僅代表非阻塞IO不能當即執行。若是howmuch爲負數,則會嘗試發送整個buffer的內容。

調用evbuffer_write,等同於以負數howmuch調用函數evbuffer_write_atmost:它會盡量的刷新buffer。

        在Unix上,這些函數能夠工做在任何支持讀寫的文件描述符上,可是在Windows上,僅能用在socket上。

注意,當使用bufferevent時,不須要調用這些IO函數,bufferevent會替你調用他們。

        

十五:evbuffer和回調函數

        使用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成員記錄了buffer大小改變以前,buffer中的字節數;n_added成員記錄了添加到buffer的字節數;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 dotevery 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_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的回調,而是使其延遲並做爲event_base的event 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的尾端添加數據:沒有進行復制,相反的,evbuffer僅僅保存指向包含datlen個字節的data的指針。所以,在evbuffer使用它期間,該指針應該保持是有效的。當evbuffer再也不須要該數據的時候,它會以data,datlen和extra爲參數,調用用戶提供的cleanupfn函數。該函數成功時返回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 thenetwork.  We do this

   without keeping any more copies of theresource in memory than

   necessary. */

 

#define  HUGE_RESOURCE_SIZE  (1024*1024)

struct huge_resource {

    /* We keep a count of the references thatexist 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 acomplex 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 thatactually adds the resource to the

   buffer. */

voidspool_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。該函數從該文件的offset位置開始,讀取length個字節,寫入到output的尾端。該函數成功時返回0,失敗是返回-1。

        警告:在Libevent2.0.x中,經過這種方式添加的數據,僅有下面幾種操做數據的方式是可靠的:經過evbuffer_write*函數將數據發送到網絡中;經過evbuffer_drain函數進行抽取數據,經過evbuffer_*_buffer函數將數據移動到其餘evbuffer中。下列操做是不可靠的:經過evbuffer_remove函數從buffer中抽取數據;經過evbuffer_pullup線性化數據等等。Libevent2.1.x會修復這些限制。

        若是操做系統支持splice和sendfile函數,則在調用evbuffer_write時,Libevent直接使用這些函數發送fd中的數據到網絡中,而不須要將數據複製到用戶內存中。若是不存在splice或sendfile函數,可是有mmap函數,則Libevent會對文件作mmap,而且內核會知道不須要將數據複製到用戶空間。若是上述函數都不存在的話,則Libevent會將數據從磁盤讀取到內存中。

        當文件中的數據刷新到evbuffer以後,或者當釋放evbuffer時,就會關閉文件描述符。若是不但願關閉文件,或者但願對文件有更細粒度的控制,則能夠參考下面的文件段(file_segment)功能。

 

十八:對文件段(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函數建立並返回一個evbuffer_file_segment對象,該對象表明了存儲在文件fd中的,以offset爲起點,共有length個字節的文件段。該函數若是發生錯誤時返回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:若是設置了該標誌,則文件段將永遠不會使用mmap類型的後端(CreateFileMapping,mmap),即便它們很是合適。

        EVBUF_FS_DISABLE_SENDFILE:若是設置了該標誌,則文件段將永遠不會使用sendfile類型的後端(sendfile,splice),即便它們很是合適。

        EVBUF_FS_DISABLE_LOCKING:若是設置了該標誌,則不會在文件段上分配鎖:在多線程環境中使用文件段將是不安全的。

        一旦獲得一個evbuffer_file_segment結構,則可使用evbuffer_add_file_segment函數將其中的一部分或者全部內容添加到evbuffer中。這裏的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);

        能夠在文件段上添加一個回調函數,當文件段的最後一個引用被釋放,而且文件段被釋放時,該回調函數被調用。該回調函數決不能在試圖從新將該文件段添加到任何buffer上。

        

十九:經過引用將evbuffer添加到另外一個evbuffer中

        能夠經過引用將evbuffer添加到另外一個evbuffer中:而不是移動一個evbuffer中內容到另外一個evbuffer中,當將evbuffer的引用添加到另外一個evbuffer中時,它的行爲相似於複製了全部字節。

int  evbuffer_add_buffer_reference(struct  evbuffer  *outbuf,

    struct  evbuffer  *inbuf);

        evbuffer_add_buffer_reference函數的行爲相似於複製outbuf中的全部數據到inbuf中,可是它卻不會執行任何沒必要要的複製。該函數成功時返回0,失敗是返回-1。

        注意,inbuf內容後續的變化將不會反饋到outbuf中:該函數是經過引用添加evbuffer當前的內容,而不是evbuffer自己。

        注意,不能嵌套buffer的引用:若是一個evbuffer是evbuffer_add_buffer_reference函數中的outbuf,則其不能做爲另外一個的inbuf。

 

二十:使一個evbuffer僅能添加或者僅能移除

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

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

        可使用這些函數暫時性的禁止evbuffer前端或後端的改變。bufferevent會在內部使用這些函數,用來防止輸出緩衝區前端,或者輸入緩衝區後端的意外改變。

 

二十一:過期的evbuffer函數

        在Libevent2.0中,evbuffer的接口發生了不少變化。在此以前,每個evbuffer都是經過一連續的內存塊實現的,這使得訪問很是低效。

        event.h頭文件之前會暴露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()函數會尋找buffer中what字符串的第一次出現,而且返回指向它的指針。不像evbuffer_search,它只會尋找第一個匹配的字符串。爲了兼容仍使用該函數的老代碼,它如今會將直到本地字符串的結尾的整個buffer進行線性化。

 

        回調的接口也再也不相同(廢棄的接口):

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只能有一個回調函數,因此設置新的回調函數會移除前一個回調,而且設置回調爲NULL,則是移除回調函數的首選方法。

        再也不是取得evbuffer_cb_info_structure結構,該函數以evbuffer的舊長度和新長度來調用。所以,若是old_len大於new_len,則數據被抽取,若是new_len大於old_len,則數據被添加。不能將回調延遲,因此,在一次回調中不能將添加和刪除進行批量化。

        這些過期的函數仍然定義在event2/buffer_compat.h中。

 

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

相關文章
相關標籤/搜索