转自:http://blog.csdn.net/windeal3203/article/details/52864994
原文:http://blog.csdn.net/windeal3203/article/details/52864994
译自:http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
libevent的`evbuffer`实现了为向后面添加数据和从前面移除数据而优化的字节队列。
`evbuffer`用于处理缓冲网络IO的“缓冲”部分。它不提供调度IO或者当IO就绪时触发IO的功能:这是bufferevent的工作。
除非特别说明,本章描述的函数都在`event2/buffer.h`中声明。
1.创建和释放evbuffer
struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);
- 1
- 2
这两个函数的功能很简明:evbuffer_new()
分配和返回一个新的空evbuffer
;而evbuffer_free()
释放evbuffer
和其内容。
这两个函数从libevent 0.8版就存在了。
2. Evbuffers 和 线程安全
int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);
- 1
- 2
- 3
默认情况下,在多个线程中同时访问evbuffer
是不安全的。如果需要这样的访问,可以在evbuffer
上调用evbuffer_enable_locking()
。如果lock
参数为NULL
,libevent
会使用evthread_set_lock_creation_callback
提供的锁创建函数创建一个锁。否则,libevent
将lock
参数用作锁。 evbuffer_lock()
和evbuffer_unlock()
函数分别请求和释放evbuffer
上的锁。可以使用这两个函数让一系列操作是原子的。如果evbuffer
没有启用锁,这两个函数不做任何操作。
(注意:对于单个操作,不需要调用evbuffer_lock()
和evbuffer_unlock()
:如果evbuffer
启用了锁,单个操作就已经是原子的。只有在需要多个操作连续执行,不让其他线程介入的时候,才需要手动锁定evbuffer
)
这些函数都在2.0.1-alpha版本中引入。
3. 检查evbuffer
size_t evbuffer_get_length(const struct evbuffer *buf);
- 1
该函数返回在evbuffer
中保存的字节数。
它在Libevent 2.0.1-alpha中引入。
size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);
- 1
这个函数返回连续地存储在evbuffer
前面的字节数。evbuffer
中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数。
它在libevent 2.0.1-alpha中引入。
4 向evbuffer中添加数据:基础
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
- 1
这个函数向buf
的末尾添加 datalen
字节的数据data
。 函数在成功时返回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);
- 1
- 2
这些函数添加格式化的数据到buf
末尾。格式参数fmt
和其他参数的处理分别与C库函数printf
和vprintf
相同。函数返回添加的字节数。
int evbuffer_expand(struct evbuffer *buf, size_t datlen);
- 1
这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳datlen
字节,而不需要更多的内存分配。
Example
/* 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);
- 1
- 2
- 3
- 4
- 5
- 6
函数evbuffer_add()
和 evbuffer_add_printf()
在Libevent0.8 中引入。
函数evbuffer_expand()
在Libevent 0.9引入, 而evbuffer_add_vprintf()
在Libevent 1.1中第一次出现。
5 从一个evbuffer
向另一个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);
- 1
- 2
- 3
函数evbuffer_add_buffer()
将src
中的所有数据移动到dst
末尾,成功时返回0,失败时返回-1。 evbuffer_remove_buffer()
函数从src
中移动datlen
字节到dst
末尾,尽量少进行复制。如果字节数小于datlen
,所有字节被移动。函数返回移动的字节数。 evbuffer_add_buffer()
在0.8版本引入;evbuffer_remove_buffer()
是2.0.1-alpha版本新增加的。
6. 添加数据到evbuffer前面
int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);
int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);
- 1
- 2
除了将数据移动到目标缓冲区前面不同外,这两个函数的行为分别与evbuffer_add()
和evbuffer_add_buffer()
相同。
使用这些函数时要当心,永远不要对与bufferevent
共享的evbuffer
使用。
这些函数是2.0.1-alpha版本新添加的。
7. 重新排列evbuffer的内部布局
有时候需要取出evbuffer
前面的N字节,将其看作连续的字节数组。要做到这一点,首先必须确保缓冲区的前面确实是连续的。
unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);'
- 1
evbuffer_pullup()
函数“线性化”buf
前面的size
字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块。如果size
是负的,函数会线性化整个缓冲区。如果size
大于缓冲区中的字节数,函数返回NULL
。否则,evbuffer_pullup()
返回指向buf
中首字节的指针。
调用evbuffer_pullup()
时使用较大的size
参数可能会非常慢,因为这可能需要复制整个缓冲区的内容。
Example
#include <event2/buffer.h>
#include <event2/util.h>
#include <string.h>
int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
{
/* Let's parse the start of a SOCKS4 request! The format is easy:
* 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
* destip. */
unsigned char *mem;
mem = evbuffer_pullup(buf, 8);
if (mem == NULL) {
/* Not enough data in the buffer */
return 0;
} else if (mem[0] != 4 || mem[1] != 1) {
/* Unrecognized protocol or command */
return -1;
} else {
memcpy(port, mem+2, 2);
memcpy(addr, mem+4, 4);
*port = ntohs(*port);
*addr = ntohl(*addr);
/* Actually remove the data from the buffer now that we know we
like it. */
evbuffer_drain(buf, 8);
return 1;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
注意:
使用 与 evbuffer_get_contiguous_space()
的返回值相等的大小 来调用evbuffer_pullup()
不会导致任何数据复制或者移动。
evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的libevent总是保证evbuffer中的数据是连续的,而不计开销。
8. 从evbuffer中删除数据
int evbuffer_drain(struct evbuffer *buf, size_t len);
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);
- 1
- 2
evbuffer_remove()
函数从buf
前面复制和移除datlen
字节到data
内存中。如果可用字节少于datlen
,函数复制所有字节。函数失败时返回-1,否则返回复制了的字节数。 evbuffer_drain()
函数的行为与evbuffer_remove()
相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除。成功时返回0,失败时返回-1。 evbuffer_drain()
由Libevent 0.8版引入,evbuffer_remove()
在Libevent0.9版首次出现。
9. 从evbuffer中拷贝出数据
有时候需要获取缓冲区前面数据的副本,而不清除数据。比如说,可能需要查看某特定类型的记录是否已经完整到达,而不清除任何数据(像evbuffer_remove
那样),或者在内部重新排列缓冲区(像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);
- 1
- 2
- 3
- 4
evbuffer_copyout()
的行为与evbuffer_remove()
类似,但是它不从缓冲区移除任何数据。也就是说,它从buf
前面复制datlen
字节到data
处的内存中。如果可用字节少于datlen
,函数会复制所有字节。失败时返回-1,否则返回复制的字节数。 evbuffer_copyout_from()
的行为与evbuffer_copyout()
有些类似, 不同的是evbuffer_copyout_from()
从制定的位置pos
开始复制, 而不是buffer
的起始位置。 从下文“在evbuffer中搜索”一节可以获取evbuffer_ptr
结构体相关的信息。
如果从缓冲区复制数据太慢,可以使用evbuffer_peek()
。
Example:
#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <stdlib.h>
int get_record(struct evbuffer *buf, size_t *size_out, char **record_out)
{
/* Let's assume that we're speaking some protocol where records
contain a 4-byte size field in network order, followed by that
number of bytes. We will return 1 and set the 'out' fields if we
have a whole record, return 0 if the record isn't here yet, and
-1 on error. */
size_t buffer_len = evbuffer_get_length(buf);
ev_uint32_t record_len;
char *record;
if (buffer_len < 4)
return 0; /* The size field hasn't arrived. */
/* We use evbuffer_copyout here so that the size field will stay on
the buffer for now. */
evbuffer_copyout(buf, &record_len, 4);
/* Convert len_buf into host order. */
record_len = ntohl(record_len);
if (buffer_len < record_len + 4)
return 0; /* The record hasn't arrived */
/* Okay, _now_ we can remove the record. */
record = malloc(record_len);
if (record == NULL)
return -1;
evbuffer_drain(buf, 4);
evbuffer_remove(buf, record, record_len);
*record_out = record;
*size_out = record_len;
return 1;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
evbuffer_copyout()
在Libevent 2.0.5-alpha中第一次出现;evbuffer_copyout_from()
在 Libevent 2.1.1-alpha.中加入。
10. 面向行的输入
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
很多互联网协议使用基于行的格式。evbuffer_readln()
函数从evbuffer
前面取出一行,用一个新分配的空字符结束的字符串返回这一行。如果n_read_out
不是NULL
,则它被设置为返回的字符串的字节数。如果没有整行供读取,函数返回空。返回的字符串不包括行结束符。 evbuffer_readln()
理解4种行结束符号:
- EVBUFFER_EOL_LF:
行尾是单个换行符(也就是0x0A
) - EVBUFFER_EOL_CRLF_STRICT:
行尾是一个回车符,后随一个换行符(也就是0x0D 0x0A
) - EVBUFFER_EOL_CRLF:
行尾是一个可选的回车,后随一个换行符(也就是说,可以是 - EVBUFFER_EOL_ANY:
行尾是任意数量、任意次序的回车和换行符。这种格式不是特别有用。它的存在主要是为了向后兼容。
(注意,如果使用event_se_mem_functions()
覆盖默认的malloc
,则evbuffer_readln
返回的字符串将由你指定的malloc
替代函数分配).
*Example:
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
evbuffer_readln()
在 Libevent 1.4.14-stable之后的版本中可用. EVBUFFER_EOL_NUL
在Libevent 2.1.1-alpha.中加入。
11. 在evbuffer中搜索
evbuffer_ptr
结构体指示evbuffer
中的一个位置,包含可用于在evbuffer
中迭代的数据。
struct evbuffer_ptr {
ev_ssize_t pos;
struct {
/* internal fields */
} _internal;
};
- 1
- 2
- 3
- 4
- 5
- 6
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
evbuffer_search()
函数在缓冲区中查找含有len
个字符的字符串what
。函数返回包含字符串位置的evbuffer_ptr
结构体,或者在没有找到字符串时返回-1。如果提供了start
参数,则从指定的位置开始搜索;否则,从开始处进行搜索。 evbuffer_search_range()
函数和evbuffer_search
行为类似,只是它只考虑在end
之前出现的what
。 evbuffer_search_eol()
函数像evbuffer_readln()
一样检测行结束,但是不复制行,而是返回指向行结束符的evbuffer_ptr
。如果eol_len_out
非空,则它被设置为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);
- 1
- 2
- 3
- 4
- 5
- 6
evbuffer_ptr_set()
函数操作buffer
中的位置pos
。如果how
等于EVBUFFER_PTR_SET
,指针被移动到缓冲区中的绝对位置position
;如果等于EVBUFFER_PTR_ADD
,则向前移动position
字节。函数成功时返回0,失败时返回-1。
Example:
#include <event2/buffer.h>
#include <string.h>
/* Count the total occurrences of 'str' in 'buf'. */
int count_instances(struct evbuffer *buf, const char *str)
{
size_t len = strlen(str);
int total = 0;
struct evbuffer_ptr p;
if (!len)
/* Don't try to count the occurrences of a 0-length string. */
return -1;
evbuffer_ptr_set(buf, &p, 0, EVBUFFER_PTR_SET);
while (1) {
p = evbuffer_search(buf, str, len, &p);
if (p.pos < 0)
break;
total++;
evbuffer_ptr_set(buf, &p, 1, EVBUFFER_PTR_ADD);
}
return total;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
警告:
任何修改evbuffer
或者其布局的调用都会使得evbuffer_ptr
失效,不能再安全地使用。
这些接口是2.0.1-alpha版本新增加的。
12. 检测数据而不复制
有时候需要读取evbuffer
中的数据而不进行(像evbuffer_copyout()
那样的)复制,也不重新排列内部内存布局(像evbuffer_pullup
()那样)。有时候可能需要查看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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
调用evbuffer_peek()
的时候,通过vec_out
给定一个evbuffer_iovec
数组,数组的长度是n_vec
。函数会让每个结构体包含指向evbuffer
内部内存块的指针(iov_base
)和块中数据长度。
如果len
小于0,evbuffer_peek()
会试图填充所有evbuffer_iovec
结构体。否则,函数会进行填充,直到使用了所有结构体,或者见到len
字节为止。如果函数可以给出所有请求的数据,则返回实际使用的结构体个数;否则,函数返回给出所有请求数据所需的结构体个数。
如果ptr
为NULL
,函数从缓冲区开始处进行搜索。否则,从ptr
处开始搜索。
Example:
{
/* Let's look at the first two chunks of buf, and write them to stderr. */
int n, i;
struct evbuffer_iovec v[2];
n = evbuffer_peek(buf, -1, NULL, v, 2);
for (i=0; i<n; ++i) { /* There might be less than two chunks available. */
fwrite(v[i].iov_base, 1, v[i].iov_len, stderr);
}
}
{
/* Let's send the first 4906 bytes to stdout via write. */
int n, i, r;
struct evbuffer_iovec *v;
size_t written = 0;
/* determine how many chunks we need. */
n = evbuffer_peek(buf, 4096, NULL, NULL, 0);
/* Allocate space for the chunks. This would be a good time to use
alloca() if you have it. */
v = malloc(sizeof(struct evbuffer_iovec)*n);
/* Actually fill up v. */
n = evbuffer_peek(buf, 4096, NULL, v, n);
for (i=0; i<n; ++i) {
size_t len = v[i].iov_len;
if (written + len > 4096)
len = 4096 - written;
r = write(1 /* stdout */, v[i].iov_base, len);
if (r<=0)
break;
/* We keep track of the bytes written separately; if we don't,
we may write more than 4096 bytes if the last chunk puts
us over the limit. */
written += len;
}
free(v);
}
{
/* Let's get the first 16K of data after the first occurrence of the
string "start
", and pass it to a consume() function. */
struct evbuffer_ptr ptr;
struct evbuffer_iovec v[1];
const char s[] = "start
";
int n_written;
ptr = evbuffer_search(buf, s, strlen(s), NULL);
if (ptr.pos == -1)
return; /* no start string found. */
/* Advance the pointer past the start string. */
if (evbuffer_ptr_set(buf, &ptr, strlen(s), EVBUFFER_PTR_ADD) < 0)
return; /* off the end of the string. */
while (n_written < 16*1024) {
/* Peek at a single chunk. */
if (evbuffer_peek(buf, -1, &ptr, v, 1) < 1)
break;
/* Pass the data to some user-defined consume function */
consume(v[0].iov_base, v[0].iov_len);
n_written += v[0].iov_len;
/* Advance the pointer so we see the next chunk next time. */
if (evbuffer_ptr_set(buf, &ptr, v[0].iov_len, EVBUFFER_PTR_ADD)<0)
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
注意:
- 修改
evbuffer_iovec
所指的数据会导致不确定的行为 - 如果任何函数修改了
evbuffer
,则evbuffer_peek()
返回的指针会失效 - 如果在多个线程中使用
evbuffer
,确保在调用evbuffer_peek()
之前使用evbuffer_lock()
,在使用完evbuffer_peek()
给出的内容之后进行解锁
这个函数是2.0.2-alpha版本新增加的。
13. 直接向evbuffer添加数据
有时候需要能够直接向evbuffer
添加数据,而不用先将数据写入到字符数组中,然后再使用evbuffer_add()
进行复制。有一对高级函数可以完成这种功能: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);
- 1
- 2
- 3
- 4
evbuffer_reserve_space()
函数给出evbuffer
内部空间的指针。函数会扩展缓冲区以至少提供size
字节的空间。到扩展空间的指针,以及其长度,会存储在通过vec
传递的向量数组中,n_vec
是数组的长度。 n_vec
的值必须至少是1。如果只提供一个vector,libevent会确保请求的所有连续空间都在单个扩展区中,但是这可能要求重新排列缓冲区,或者浪费内存。为取得更好的性能,应该至少提供2个向量。函数返回提供请求的空间所需的向量数。
写入到向量中的数据不会是缓冲区的一部分,直到调用evbuffer_commit_space()
,使得写入的数据进入缓冲区。如果需要提交少于请求的空间,可以减小任何evbuffer_iovec
结构体的iov_len
字段,也可以提供较少的向量。函数成功时返回0,失败时返回-1。
提示和警告:
- 调用任何重新排列
evbuffer
或者向其添加数据的函数都将使之前从evbuffer_reserve_space()
获取的指针失效。 - 当前实现中,不论用户提供多少个向量,
evbuffer_reserve_space()
从不使用多于两个。这一点在未来版本可能会有变化。 - 如果在多个线程中使用
evbuffer
,确保在调用evbuffer_reserve_space()
之前使用evbuffer_lock()
进行锁定,然后在提交后解除锁定。
Example:
/* Suppose we want to fill a buffer with 2048 bytes of output from a
generate_data() function, without copying. */
struct evbuffer_iovec v[2];
int n, i;
size_t n_to_add = 2048;
/* Reserve 2048 bytes.*/
n = evbuffer_reserve_space(buf, n_to_add, v, 2);
if (n<=0)
return; /* Unable to reserve the space for some reason. */
for (i=0; i<n && n_to_add > 0; ++i) {
size_t len = v[i].iov_len;
if (len > n_to_add) /* Don't write more than n_to_add bytes. */
len = n_to_add;
if (generate_data(v[i].iov_base, len) < 0) {
/* If there was a problem during data generation, we can just stop
here; no data will be committed to the buffer. */
return;
}
/* Set iov_len to the number of bytes we actually wrote, so we
don't commit too much. */
v[i].iov_len = len;
}
/* We commit the space here. Note that we give it 'i' (the number of
vectors we actually used) rather than 'n' (the number of vectors we
had available. */
if (evbuffer_commit_space(buf, v, i) < 0)
return; /* Error committing */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
Bad Example:
/* Here are some mistakes you can make with evbuffer_reserve().
DO NOT IMITATE THIS CODE. */
struct evbuffer_iovec v[2];
{
/* Do not use the pointers from evbuffer_reserve_space() after
calling any functions that modify the buffer. */
evbuffer_reserve_space(buf, 1024, v, 2);
evbuffer_add(buf, "X", 1);
/* WRONG: This next line won't work if evbuffer_add needed to rearrange
the buffer's contents. It might even crash your program. Instead,
you add the data before calling evbuffer_reserve_space. */
memset(v[0].iov_base, 'Y', v[0].iov_len-1);
evbuffer_commit_space(buf, v, 1);
}
{
/* Do not modify the iov_base pointers. */
const char *data = "Here is some data";
evbuffer_reserve_space(buf, strlen(data), v, 1);
/* WRONG: The next line will not do what you want. Instead, you
should _copy_ the contents of data into v[0].iov_base. */
v[0].iov_base = (char*) data;
v[0].iov_len = strlen(data);
/* In this case, evbuffer_commit_space might give an error if you're
lucky */
evbuffer_commit_space(buf, v, 1);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
这个函数及其提出的接口从2.0.2-alpha版本就存在了。
14. 使用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);
- 1
- 2
- 3
- 4
evbuffer_read()
函数从套接字fd
读取至多howmuch
字节到buffer
末尾。函数成功时返回读取的字节数,0表示EOF,失败时返回-1。注意,错误码可能指示非阻塞操作不能立即成功,应该检查错误码EAGAIN
(或者Windows中的WSAWOULDBLOCK
)。如果howmuch
为负,evbuffer_read()
会尝试猜测要读取多少数据。 evbuffer_write_atmost()
函数试图将buffer
前面至多howmuch
字节写入到套接字fd
中。成功时函数返回写入的字节数,失败时返回-1。跟evbuffer_read()
一样,应该检查错误码,看是真的错误,还是仅仅指示非阻塞IO不能立即完成。如果为howmuch
给出负值,函数会试图写入buffer
的所有内容。
调用evbuffer_write()
与使用负的howmuch
参数调用evbuffer_write_atmost()
一样:函数会试图尽量清空buffer
的内容。
在Unix中,这些函数应该可以在任何支持read
和write
的文件描述符上正确工作。在Windows中,仅仅支持套接字。
注意,如果使用bufferevent
,则不需要调用这些函数,bufferevent
的代码已经为你调用了。 evbuffer_write_atmost()
函数在2.0.1-alpha版本中引入。
15. 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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
回调函数在 有数据被添加到evbuffer
中,或者从中移除数据的时候,会被调用。函数收到缓冲区指针、一个evbuffer_cb_info
结构体指针,和用户提供的参数。evbuffer_cb_info
结构体的orig_size
字段指示缓冲区改变大小前的字节数,n_added
字段指示向缓冲区添加了多少字节;n_deleted
字段指示移除了多少字节。
struct evbuffer_cb_entry;
struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer,
evbuffer_cb_func cb, void *cbarg);
- 1
- 2
- 3
evbuffer_add_cb()
函数为evbuffer
添加一个回调函数,返回一个不透明的指针,随后可用于代表这个特定的回调实例。cb
参数是将被调用的函数,cbarg
是用户提供的将传给这个函数的指针。
可以为单个evbuffer
设置多个回调,添加新的回调不会移除原来的回调。
Example:
#include <event2/buffer.h>
#include <stdio.h>
#include <stdlib.h>
/* Here's a callback that remembers how many bytes we have drained in
total from the buffer, and prints a dot every time we hit a
megabyte. */
struct total_processed {
size_t n;
};
void count_megabytes_cb(struct evbuffer *buffer,
const struct evbuffer_cb_info *info, void *arg)
{
struct total_processed *tp = arg;
size_t old_n = tp->n;
int megabytes, i;
tp->n += info->n_deleted;
megabytes = ((tp->n) >> 20) - (old_n >> 20);
for (i=0; i<megabytes; ++i)
putc('.', stdout);
}
void operation_with_counted_bytes(void)
{
struct total_processed *tp = malloc(sizeof(*tp));
struct evbuffer *buf = evbuffer_new();
tp->n = 0;
evbuffer_add_cb(buf, count_megabytes_cb, tp);
/* Use the evbuffer for a while. When we're done: */
evbuffer_free(buf);
free(tp);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
注意:释放非空evbuffer
不会清空其数据,释放evbuffer
也不会为回调释放用户提供的数据指针。
如果不想让缓冲区上的回调永远激活,可以移除或者禁用回调:
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
可以通过添加回调时候获取的evbuffer_cb_entry
来移除回调,也可以通过回调函数和参数指针来移除。成功时函数返回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);
- 1
跟bufferevent
回调一样,可以让evbuffer
回调不在evbuffer
被修改时立即运行,而是延迟到某event_base
的事件循环中执行。如果有多个evbuffer
,它们的回调潜在地让数据添加到evbuffer
中,或者从中移除,又要避免栈崩溃,延迟回调是很有用的。
如果回调被延迟,则最终执行时,它可能是多个操作结果的总和。
与bufferevent
一样,evbuffer
具有内部引用计数的,所以即使还有未执行的延迟回调,释放evbuffer
也是安全的。
整个回调系统是2.0.1-alpha版本新引入的。evbuffer_cb_(set|clear)_flags()
函数从2.0.2-alpha版本开始存在。
16. 为基于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);
- 1
- 2
- 3
- 4
- 5
- 6
这个函数通过引用向evbuffer
末尾添加一段数据。不会进行复制:evbuffer
只会存储一个到data
处的datlen
字节的指针。因此,在evbuffer
使用这个指针期间,必须保持指针是有效的。evbuffer
会在不再需要这部分数据的时候调用用户提供的cleanupfn
函数,带有提供的data
指针、datlen
值和extra
指针参数。函数成功时返回0,失败时返回-1。
Example:
#include <event2/buffer.h>
#include <stdlib.h>
#include <string.h>
/* In this example, we have a bunch of evbuffers that we want to use to
spool a one-megabyte resource out to the network. We do this
without keeping any more copies of the resource in memory than
necessary. */
#define HUGE_RESOURCE_SIZE (1024*1024)
struct huge_resource {
/* We keep a count of the references that exist to this structure,
so that we know when we can free it. */
int reference_count;
char data[HUGE_RESOURCE_SIZE];
};
struct huge_resource *new_resource(void) {
struct huge_resource *hr = malloc(sizeof(struct huge_resource));
hr->reference_count = 1;
/* Here we should fill hr->data with something. In real life,
we'd probably load something or do a complex calculation.
Here, we'll just fill it with EEs. */
memset(hr->data, 0xEE, sizeof(hr->data));
return hr;
}
void free_resource(struct huge_resource *hr) {
--hr->reference_count;
if (hr->reference_count == 0)
free(hr);
}
static void cleanup(const void *data, size_t len, void *arg) {
free_resource(arg);
}
/* This is the function that actually adds the resource to the
buffer. */
void spool_resource_to_evbuffer(struct evbuffer *buf,
struct huge_resource *hr)
{
++hr->reference_count;
evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE,
cleanup, hr);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
evbuffer_add_reference()
从2.0.2-alpha.开始引入。
17. 向evbuffer添加文件
一些操作系统提供了将文件写入到网络,而不需要将数据复制到用户空间的方法。如果存在,可以使用下述接口访问这种机制:
int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
size_t length);
- 1
- 2
evbuffer_add_file()
要求一个打开的可读文件描述符fd
(注意:不是套接字)。函数将文件中offset
处开始的length
字节添加到output
末尾。成功时函数返回0,失败时返回-1。
*注意:
在2.0.2-alpha版中,对于使用这种方式添加的数据的可靠操作只有:通过evbuffer_write*()
将其发送到网络、使用evbuffer_drain()
清空数据,或者使用evbuffer_*_buffer()
将其移动到另一个evbuffer
中。不能使用evbuffer_remove()
取出数据,或使用evbuffer_pullup()
进行线性化等。
如果操作系统支持splice()
或者sendfile()
,则调用evbuffer_write()
时libevent会直接使用这些函数来将来自fd
的数据发送到网络中,而根本不将数据复制到用户内存中。如果不存在splice()
和sendfile()
,但是支持mmap()
,libevent将进行文件映射,而内核将意识到永远不需要将数据复制到用户空间。否则,libevent会将数据从磁盘读取到内存。
清空数据或者释放evbuffer
时文件描述符将被关闭。
这一节描述的函数都在2.0.1-alpha版本中引入。
18. 文件片段的细粒度控制
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
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);
- 1
- 2
- 3
- 4
- 5
可以在文件段上添加一个回调函数,当文件段的最后一个引用被释放,并且文件段被释放时,该回调函数被调用。该回调函数决不能在试图重新将该文件段添加到任何buffer上。
这些文件段函数第一次出现在Libevent 2.1.1-alpha; evbuffer_file_segment_add_cleanup_cb()
在 2.1.2-alpha中加入
19. 通过引用将evbuffer添加到另一个evbufer中
也可以通过引用将evbuffer
添加到另一个evbuffer
中:而不是移动一个evbuffer
中内容到另一个evbuffer
中,当将evbuffer
的引用添加到另一个evbuffer
中时,它的行为就好像复制了所有字节一样。
int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
struct evbuffer *inbuf);
- 1
- 2
evbuffer_add_buffer_reference()
函数的行为就好像你已经从 outbuf
复制了所有字节到 inbuf
, 但实际上并没有进行任何不必要的拷贝。 .函数在成功是返回0, 失败时返回-1.
注意,inbuf内容后续的变化将不会反馈到outbuf中:该函数是通过引用添加evbuffer当前的内容,而不是evbuffer本身。
注意,不能嵌套buffer的引用:如果一个evbuffer是evbuffer_add_buffer_reference函数中的outbuf,则其不能作为另一个的inbuf。
这个函数在Libevent 2.1.1-alpha中引进。
20.使一个evbuffer仅能添加或者仅能移除
int evbuffer_freeze(struct evbuffer *buf, int at_front);
int evbuffer_unfreeze(struct evbuffer *buf, int at_front);
- 1
- 2
可以使用这些函数暂时性的禁止evbuffer
前端或后端的改变。bufferevent
会在内部使用这些函数,用来防止输出缓冲区前端,或者输入缓冲区后端的意外改变。
evbuffer_freeze()
函数在Libevent 2.0.1-alpha引入。
21. 废弃的函数
在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);
- 1
- 2
- 3
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);
- 1
- 2
- 3
同一时刻一个evbuffer
只能有一个回调函数,所以设置新的回调函数会移除前一个回调,并且设置回调为NULL
,则是移除回调函数的首选方法。
与取得evbuffer_cb_info_structure
结构不同,该函数以evbuffer
的旧长度和新长度来调用。因此,如果old_len
大于new_len
,则数据被抽取,如果new_len
大于old_len
,则数据被添加。不能将回调延迟,所以,在一次回调中不能将添加和删除进行批量化。
这些废弃的函数在event2/buffer_compat.h.
中依然可用。