返回主目录
Libevent
快速可移植非阻塞式网络编程
修订历史
版本
日期
作者
备注
V1.0
2016-11-15
周勇
Libevent编程中文帮助文档
文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.
此外,本文档的源代码示例也是基于BSD的"3条款"或"修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:
英文:http://libevent.org/
中文:http://blog.csdn.net/zhouyongku/article/details/53431750
请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.
<<上一章>>
LibEvent的bufferevent实现了向后添加数据和前面移除数据的优化字节序列.evbuffer用于处理完了IO的缓冲部分.它不提供调度IO或当IO就绪时触发IO的功能:这就是bufferevent所做的事.
除了特定说明外,本章的函数定义在<event2/buffer.h>中.
接口
struct evbuffer* evbuffer_new(void); void evbuffer_free(struct evbuffer* buf);这些函数应该相当清晰:evbuffer_new()分配了一个空的evbuffer,evbuffer_free()删除一个或全部的内容.
这些函数自从LibEvent0.8就存在.
接口
int evbuffer_enable_locking(struct evbuffer* buf, void * lock); void evbuffer_lock(struct evbuffer* buf); void evbuffer_unlock(struct evbuffer* buf);默认情况下多线程同时访问一个evbuffer是不安全的,如果非得这样,可以为evbuffer调用evbuffer_enable_locking()默认情况下,在多个线程中同时访问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版本中引入.
接口
size_t evbuffer_get_length(const struct evbuffer* buf);这个函数返回 evbuffer存储的字节数,它在2.0.1-alpha版本中引入.
接口
size_t evbuffer_get_contiguous_space(const struct evbuffer* buf);这个函数返回连续地存储在 evbuffer前面的字节数. evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数.
这个函数在2.0.1-alpha版本引入.
接口
int evbuffer_add(struct evbuffer* buf, const void * data, size_t datlen);这个函数添加 data处的 datalen 字节到 buf 的末尾,成功时返回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末尾.格式参数和其他参数的处理分别与C 库函数printf和vprintf 相同.函数返回添加的字节数.
接口
int evbuffer_expand(struct evbuffer* buf, size_t datlen);这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳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_add()和evbuffer_add_printf()函数在libevent 0.8版本引入;evbuffer_expand()首次出现在0.9版本,而evbuffer_add_printf()首次出现在1.1版本.
为提高效率,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_add_buffer()在0.8版本引入;evbuffer_remove_buffer()是2.0.1-alpha版本新增加的.
接口
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()相同.
使用这些函数时要当心,永远不要对与bufferevent 共享的evbuffer 使用.这些函数是2.0.1-alpha版本新添加的.
有时候需要取出 evbuffer前面的 N 字节,将其看作连续的字节数组.要做到这一点,首先必须确保缓冲区的前面确实是连续的.
接口
unsigned char* evbuffer_pullup(struct evbuffer * buf, ev_ssize_t size);evbuffer_pullup()函数"线性化"buf前面的 size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块.如果size 是负的,函数会线性化整个缓冲区.如果size 大于缓冲区中的字节数,函数返回NULL.否则,evbuffer_pullup()返回指向buf 中首字节的指针.
调用 evbuffer_pullup()时使用较大的size 参数可能会非常慢,因为这可能需要复制整个缓冲区的内容.
示例
#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; } }提示使用 evbuffer_get_contiguous_space()返回的值作为尺寸值调用evbuffer_pullup()不会导致任何数据复制或者移动.
evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的 libevent总是保证 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 处的内存中.如果可用字节少于datlen,函数复制所有字节.失败时返回-1,否则返回复制了的字节数.
evbuffer_drain()函数的行为与evbuffer_remove()相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除.成功时返回0,失败时返回-1.
evbuffer_drain()由0.8版引入,evbuffer_remove()首次出现在0.9版.
有时候需要获取缓冲区前面数据的副本,而不清除数据.比如说,可能需要查看某特定类型的记录是否已经完整到达,而不清除任何数据(像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);evbuffer_copyout()的行为与evbuffer_remove()相同,但是它不从缓冲区移除任何数据.也就是说,它从 buf前面复制datlen 字节到data 处的内存中.如果可用字节少于 datlen ,函数会复制所有字节.失败时返回-1,否则返回复制的字节数.如果从缓冲区复制数据太慢,可以使用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 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; }
接口
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 前面取出一行,用一个新分配的空字符结束的字符串返回这一行.如果n_read_out 不是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:行尾是任意数量、任意次序的回车和换行符.这种格式不是特别有用.它的存在主要是为了向后兼容.
注意如果使用 event_se_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_readln()接口在1.4.14-stable及以后版本中可用.
evbuffer_ptr结构体指示 evbuffer中的一个位置,包含可用于在evbuffer 中迭代的数据.
接口
struct evbuffer_ptr { ev_ssize_t pos; struct { /* internal fields*/ } _internal; };接口
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()函数在缓冲区中查找含有len 个字符的字符串what. 函数返回包含字符串位置,或者在没有找到字符串时包含-1的evbuffer_ptr 结构体.如果提供了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);
evbuffer_ptr_set函数操作 buffer中的位置 pos.如果how 等于EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置position;如果等于EVBUFFER_PTR_ADD,则向前移动position字节.成功时函数返回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 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; }警告任何修改 evbuffer或者其布局的调用都会使得 evbuffer_ptr失效,不能再安全地使用.这些接口是2.0.1-alpha版本新增加的.
有时候需要读取 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);调用 evbuffer_peek()的时候,通过vec_out 给定一个evbuffer_iovec 数组,数组的长度是n_vec.函数会让每个结构体包含指向evbuffer 内部内存块的指针(iov_base)和块中数据长度.
如果 len小于0,evbuffer_peek()会试图填充所有evbuffer_iovec 结构体.否则,函数会进行填充,直到使用了所有结构体,或者见到len 字节为止.如果函数可以给出所有请求的数据,则返回实际使用的结构体个数;否则,函数返回给出所有请求数据所需的结构体个数.
如果 ptr为 NULL,函数从缓冲区开始处进行搜索.否则,从ptr 处开始搜索.
示例
{ /* 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\n", and pass it to 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-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; } }注意修改 evbuffer_iovec所指的数据会导致不确定的行为如果任何函数修改了 evbuffer,则evbuffer_peek()返回的指针会失效如 果 在 多 个 线 程 中 使 用evbuffer , 确 保 在 调 用evbuffer_peek() 之 前 使 用evbuffer_lock(),在使用完evbuffer_peek()给出的内容之后进行解锁.
这个函数是2.0.2-alpha版本新增加的.
有时候需要能够直接向 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);evbuffer_reserve_space()函数给出evbuffer 内部空间的指针.函数会扩展缓冲区以至少提供size 字节的空间.到扩展空间的指针,以及其长度,会存储在通过vec 传递的向量数组中,n_vec是数组的长度.
n_vec的值必须至少是1.如果只提供一个向量,libevent会确保请求的所有连续空间都在单个扩展区中,但是这可能要求重新排列缓冲区,或者浪费内存.为取得更好的性能,应该至少提供2个向量.函数返回提供请求的空间所需的向量数.
写入到向量中的数据不会是缓冲区的一部分,直到调用evbuffer_commit_space(),使得写入的数据进入缓冲区.如果需要提交少于请求的空间,可以减小任何 evbuffer_iovec结构体的 iov_len字段,也可以提供较少的向量.函数成功时返回0,失败时返回-1.
提示和警告:
调用 任 何 重 新排 列 evbuffer或 者 向 其 添 加 数 据 的 函 数 都 将 使 从evbuffer_reserve_space()获取的指针失效.
当前实现中,不论用户提供多少个向量,evbuffer_reserve_space()从不使用多于两个.未来版本可能会改变这一点.
如果在多个线程中使用 evbuffer,确保在调用evbuffer_reserve_space()之前使用evbuffer_lock()进行锁定,然后在提交后解除锁定
示例
/* 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*/
糟糕的示例
/* 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’relucky*/ evbuffer_commit_space(buf, v, 1); }这个函数及其提出的接口从2.0.2-alpha版本就存在了.
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 末尾.成功时函数返回读取的字节数,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版本中引入.
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_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);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 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); }注意:释放非空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);可以通过添加回调时候的 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);跟 bufferevent回调一样,可以让evbuffer 回调不在evbuffer 被修改时立即运行,而是延迟到某event_base 的事件循环中执行.如果有多个evbuffer,它们的回调潜在地让数据添加到evbuffer 中,或者从中移除,又要避免栈崩溃,延迟回调是很有用的.
如果回调被延迟,则最终执行时,它可能是多个操作结果的总和.
与 bufferevent一样,evbuffer具有内部引用计数的,所以即使还有未执行的延迟回调,释放evbuffer 也是安全的.
整个 回 调 系 统是 2.0.1-alpha 版 本 新 引 入 的. evbuffer_cb_(set|clear)_flags() 函 数 从
2.0.2-alpha版本开始存在.
真正高速的网络编程通常要求尽量少的数据复制,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只会存储一个到data处的 datlen字节的指针.因此,在evbuffer 使用这个指针期间,必须保持指针是有效的.evbuffer会在不再需要这部分数据的时候调用用户提供的 cleanupfn函数,带有提供的data指针、datlen值和 extra指针参数.函数成功时返回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 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); }一些操作系统提供了将文件写入到网络,而不需要将数据复制到用户空间的方法.如果存在 ,可以使用下述接口访问这种机制:
evbuffer_add_reference()函数在LibEvent2.0.2-alpha版本中开始出现.
一些操作系统提供了直接写文件到网络的而不需要从文件拷贝数据到用户数据区的方法.可以用一些简单的接口使用这些机制.
接口
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.x版本中,唯一可靠的处理数据的补充方式是用evbuffer_write*()发送到网络,用evbuffer_drain()排掉或者用evbuffer_*_buffer*()来移动到另外一个evbuffer上.不能使用evbuffer_remove()可靠地提取数据或使用evbuffer_pullup()来线性化数据等等.LibEvent2.1x试图修复这个限制.
如果你的操作系统支持splice()或者sendfile()函数,LibEvent将会使用这些函数从fd套接字上直接发送数据到网络而不用经过RAM.如果你的操作系统不支持splice()或者sendfile()函数但是支持mmap()函数,LibEvent将会对文件进行内存映射,那么你的内核将会有希望判定其不需要拷贝数据到用户内存空间,否则LibEvent将会从磁盘读取数据到RAM中.
在数据刷新到evbuffer()中之后或者evbuffer释放的时候文件描述符将会关闭,如果你不想这样而想要更细粒度地控制文件,请查看下面的函数.
本函数定义在LibEvent2.0.1-alpha版本中.
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:如果设置了这个标志,file_segment将不会对文件使用内存映射的后台模式,即使内存映射更合适.
EVBUF_FS_DISABLE_SENDFILE:如果设置了这个标志,file_segment将不会对文件使用使用sendfile后台模式,即使sendfile更合适.
EVBUF_FS_DISABLE_LOCKING:如果设置了这个标志,file_segment将不会再分配锁:多线程中以任何方式使用它将会使得它不再安全.
一旦有了evbuffer_file_segment,可以用evbuffer_add_file_segment()来添加一些或所有evbuffer.这里的参数offset指的是文件片段内的偏移量而不是文件的偏移量.
接口
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等.
这些文件段函数首次出现在LibEvent2.1.1-alpha,evbuffer_file_segment_add_cleanup_cb()首次出现在LibEvent2.1.2-alpha.
可以添加一个引用到另一个Evbuffer:比起移动buffer的内容并且将它们添加到另一个,你可以为另一个添加一个引用,就好像你拷贝了所有的内部字节.
接口
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_add_buffer_reference的outbuf不能成为别的buffer的inbuf.
这个函数首次出现在LibEvent2.1.1-alpha中.
接口
int evbuffer_freeze(struct evbuffer* buf, int at_front); int evbuffer_unfreeze(struct evbuffer* buf, int at_front);你可以使用这些函数来临时禁用改变evbuffer的头或尾.bufferevent代码使用这些函数内部来阻止意外编辑输出buffer的头或者输入buffer的尾.
evbuffer_freeze()函数首次出现在LibEvent2.0.1-alpha版本.
evbuffer的接口在LibEvent2.0已经有了较大改变.在此之前每个evbuffer被实现为一个连续的块内存,访问效率很低.
event.h头文件常用来显示evbuffer的内部结构,然而这些都是不再可用的,在1.4和2.0版本之中依赖evbuffer工作的函数都有了很大的改变.
要访问evbuffer字节的数量,可以使用EVBUFFER_LENGTHH()宏,实际数据可以使用EVBUFFER_DATA()宏,这两个宏都在event2/bufer_compat.h头文件中.不过,小心EVBUFFER_DATA(b)是evbuvfer_pullup()别名,用它的代价比较昂贵.
别的一些弃用的接口:
接口
char* evbuffer_readline(struct evbuffer * buffer); unsigned char* evbuffer_find(struct evbuffer * buffer, const unsigned char* what, size_t len);evbuffer_find()函数工作原理与当前的evbuffer_readln(buffer,NULL,EVBUFFER_EOL_ANY)类似.
evbuffer_find()函数会搜索在buffer中出现的字符串并返回指向该字符串的指针.与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只能一次设置一个回调函数,所以设置一个新的回调函数将会使之前的回调函数失效,设置回调函数为空则将使回调函数禁用.
函数调用的时候使用新老版本的evbuffer的长度而不是获取evbuffer_cb_info_structure,因此如果old_len比new_len大那么数据将会流失,如果new_len比old_len大数据将被添加.由于不可能推迟一个回调调用,因此增加或删除操作将 不会被批处理成一个单个回调函数调用.
这些废弃的函数仍然在event2/buffer_compatch.h中有效.
<<下一章>>