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