Libevent的evbuffer功能实现了一个字节队列,优化了在队列尾端增加数据,以及从队列前端删除数据的操作。

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

void  evbuffer_lock(struct  evbuffer  *buf);

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置为内存块的长度。

,则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

Libevent:9Evbuffers缓存IO的实用功能的更多相关文章

  1. 磁盘IO:缓存IO与直接IO

    文件系统IO分为DirectIO和BufferIO,其中BufferIO也叫Normal IO. 1. 缓存IO 缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O.在Linu ...

  2. 不带缓存IO和标准(带缓存)IO

    linux对IO文件的操作分为: 不带缓存:open read.posix标准,在用户空间没有缓冲,在内核空间还是进行了缓存的.数据-----内核缓存区----磁盘 假设内核缓存区长度为100字节,你 ...

  3. NFS缓存IO机制

    NFS的缓存IO机制<一> async 参数模式下分析 NFS 默认的mount参数为async,async 参数表示内核不会透传程序的IO请求给sever,对于写IO会延迟执行,积累一定 ...

  4. Libevent的IO复用技术和定时事件原理

    Libevent 是一个用C语言编写的.轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大:源代码相当精炼.易 ...

  5. 深究标准IO的缓存

    前言 在最近看了APUE的标准IO部分之后感觉对标准IO的缓存太模糊,没有搞明白,APUE中关于缓存的部分一笔带过,没有深究缓存的实现原理,这样一本被吹上天的书为什么不讲透彻呢?今天早上爬起来赶紧找了 ...

  6. libevent 网络IO分析

    libevent 网络IO分析 Table of Contents 1. 简介 2. 简单使用与入门 2.1. 定时器-timeout 超时回调 2.2. 信号事件 2.3. 读取 socket 3. ...

  7. libevent源码分析一--io事件响应

    这篇文章将分析libevent如何组织io事件,如何捕捉事件的发生并进行相应的响应.这里不会详细分析event与event_base的细节,仅描述io事件如何存储与如何响应. 1.  select l ...

  8. 网站缓存技术总结( ehcache、memcache、redis对比)

    网站技术高速发展的今天,缓存技术已经成为大型网站的一个关键技术,缓存设计好坏直接关系的一个网站访问的速度,以及购置服务器的数量,甚至影响到用户的体验. 网站缓存按照存放的地点不同,可以分为客户端缓存. ...

  9. ehcache memcache redis 三大缓存

    最近项目组有用到这三个缓存,去各自的官方看了下,觉得还真的各有千秋!今天特意归纳下各个缓存的优缺点,仅供参考!  Ehcache 在Java项目广泛的使用.它是一个开源的.设计于提高在数据从RDBMS ...

随机推荐

  1. PHP苹果推送实现(APNS)

    以下资料网上收集整理得来 1.在ios dev center制作相关证书和文件用客户端实现(不再赘述,网上很多,) 网上教程: http://blog.csdn.net/lizhenning87/ar ...

  2. 理解 Python 语言中的 defaultdict

    众所周知,在Python中如果访问字典中不存在的键,会引发KeyError异常(JavaScript中如果对象中不存在某个属性,则返回undefined).但是有时候,字典中的每个键都存在默认值是非常 ...

  3. PHP搜索优化 sphinx 搭建测试

    安装.环境:win7 64位 1.下载sphinx文件包 下载地址:http://sphinxsearch.com/downloads/archive/ 2.解压到D:/sphinx.新建文件夹dat ...

  4. Java中字符串为什么不以\0结尾

    Java中字符串为什么不以\0结尾 其实这个问题没有什么好说的,Java里面一切都是对象,是对象的话,字符串肯定就有长度,即然有长度,编译器就可以确定要输出的字符个数,当然也就没有必要去浪费那1字节的 ...

  5. jeecms之全文检索

    需要在后台生成检索,如下: . 这样,在首页进行搜索的时候才会显示如下: 注意,一定要先生成索引,才能进行全文检索.  

  6. Asp.Net Core2.0在linux下发布

    一.在linux上新建mvc项目发布 可以参考:https://segmentfault.com/a/1190000012428781 也可以看微软官方文档. 大致步骤如下: 1.在linux下安装. ...

  7. NACOS集群搭建遇到的问题

    搭建NACOS官网教程: https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 这里说的很详细了.也有中文的.我就记录一下在搭建集群的时候 ...

  8. wordpress设置一个特定的页面作为首页

    首先在"页面"里新建一个页面,比如标题为"welcome"; 然后在设置里找到"阅读",首页显示调整为"一个静态页面", ...

  9. 二维vector基本使用

    变量声明 vector<vector<int> > 变量名: 添加行 vector<vector<int> > v2d; for(int i=0;i&l ...

  10. mysql全连接

    Oracle数据库支持full join,mysql是不支持full join的,但仍然可以同过左外连接+ union+右外连接实现 SELECT * FROM t1 LEFT JOIN t2 ON ...