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結構pos爲buffer中的某個位置。若是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