Evbuffers:缓冲 IO 的实用程序功能

简介

Libevent 的 evbuffer 功能实现了一个字节队列, 优化了将数据添加到末尾并从前面删除数据。

Evbuffers 通常可用于执行“缓冲区” 缓冲网络 IO 的一部分。它们不提供以下功能 安排 IO 或在 IO 准备就绪时触发 IO:就是这样 Buffer事件确实如此。

本章中的函数在 event2/buffer.h 中声明,除非 另有说明。

创建或释放 evbuffer

接口

struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);

这些函数应该比较清楚: evbuffer_new() 分配 并返回一个新的空 evbuffer,evbuffer_free() 删除一个和 它的所有内容。

这些函数从 Libevent 0.8 开始就已经存在了。

Evbuffers 和线程安全

接口

int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);

默认情况下,从多个线程访问 evbuffer 是不安全的 。如果需要多个线程同时访问,可以调用 evbuffer_enable_locking() 在 evbuffer 上。如果其 lock 参数为 NULL,则 Libevent使用evthread_set_lock_creation_callback提供创建函数分配新锁 。否则 它使用参数作为锁。

evbuffer_lock() 和 evbuffer_unlock() 函数获取和 分别释放 EVBUFFER 上的锁。您可以使用它们来 使一组操作成为原子操作。如果尚未启用锁定 evbuffer,这些函数不执行任何操作。

(请注意,您不需要调用 evbuffer_lock() 和 evbuffer_unlock() 围绕单个操作:如果锁定是 在 EVSuBuffer 上启用,各个操作已经是原子操作。 您只需要手动锁定 evbuffer 时,您才需要手动锁定 evbuffer 一个需要在没有另一个线程对接的情况下执行的操作。

这些函数都是在 Libevent 2.0.1-alpha 中引入的。

检查 evbuffer

接口

size_t evbuffer_get_length(const struct evbuffer *buf);

此函数返回存储在 evbuffer 中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

接口

size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

此函数返回连续存储在evbuffer前面的字节数。evbuffer中的字节可以存储在多个单独的存储器块中;此函数返回当前存储在第一个块中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

向 evbuffer 添加数据:基础知识

接口

int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);

此函数将数据中的 datlen 字节追加到 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() 在 Libevent 0.9 中,并且 evbuffer_add_vprintf() 首次出现在 Libevent 1.1 中。

将数据从一个 evbuffer 移动到另一个 evbuffer

为了提高效率,Libevent 优化了从 一个 evbuffer 到另一个 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() 函数精确地移动 datlen 字节 从 src到 dst 的末尾,尽可能少地复制。如果 要移动的字节数少于 datlen,它会移动所有字节。 它返回移动的字节数。

我们在 Libevent 0.8 中引入了 evbuffer_add_buffer(); evbuffer_remove_buffer() 是 Libevent 2.0.1-alpha 中的新功能。

将数据添加到 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() 除了它们将数据移动到 目标缓冲区。

这些函数应谨慎使用,切勿在 evbuffer 上使用 与 BufferEvent 共享。它们是 Libevent 2.0.1-alpha 中的新功能。

重新排列 evbuffer 的内部布局

有时,您希望查看evbuffer前面的前N个字节的数据,并将其视为一个连续的字节数组。要做到这一点,必须首先确保缓冲区的前面确实是连续的。。

接口

unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);

evbuffer_pullup() 函数将 buf 的第一个大小字节“线性化”,根据需要复制或移动它们以确保它们都是 连续并占用相同的内存块。如果大小是 负数,该函数将整个缓冲区线性化。如果大小是 大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup() 返回指向第一个的指针 BUF 中的字节。

调用大大小的 evbuffer_pullup() 可能会很慢,因为 它可能需要复制整个缓冲区的内容。

#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_pullup(),其大小等于 返回的值 evbuffer_get_contiguous_space() 不会导致任何数据被 复制或移动。

evbuffer_pullup() 函数是 Libevent 2.0.1-alpha 中的新功能: 以前版本的 Libevent 始终保持 evbuffer 数据连续, 不计成本。

从 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。

Libevent 0.8 引入了 evbuffer_drain();evbuffer_remove() 出现在 libevent 0.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);

evbuffer_copyout() 的行为与 evbuffer_remove() 类似,但不从缓冲区中删除所有数据。也就是说,它将 buf 前面的第一个 datlen 字节复制到 data 的内存中。如果有 少于 DATLEN 字节数,该函数将复制所有字节 有。失败时返回值为 -1,否则为 复制的字节数。

evbuffer_copyout_from() 函数的行为类似于 evbuffer_copyout(),但 它不是从缓冲区的前面复制字节,而是复制它们 从 pos 中提供的位置开始。请参阅“在 evbuffer“,以获取有关evbuffer_ptr结构的信息。

如果从缓冲区复制数据的速度太慢,请改用 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;
}

evbuffer_copyout() 函数首次出现在 Libevent 2.0.5-alpha 中; evbuffer_copyout_from() 是在 Libevent 2.1.1-alpha 中添加的。

面向行的输入

接口

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

许多 Internet 协议使用基于行的格式。evbuffer_readln() 函数从 evbuffer 的前面提取一行并返回它 在新分配的 NUL终止字符串中。如果不是n_read_out NULL,**n_read_out* 设置为字符串中的字节数 返回。如果没有要读取的整行,则函数返回 零。行终止符不包含在复制的字符串中。

evbuffer_readln() 函数理解 4 种行终止格式:

  • EVBUFFER_EOL_LF

    行尾是单个换行符。(这也是 称为“\n”。它是 ASCII 值为 0x0A。

  • EVBUFFER_EOL_CRLF_STRICT

    一行的末尾是单回车,后跟一个 单行换行。(这也称为“\r\n”。ASCII 值 是0x0D 0x0A)。

  • EVBUFFER_EOL_CRLF

    该行的末尾是可选的回车符,后跟 换行。(换言之,它要么是“\r\n”,要么是“\n”。 此格式在分析基于文本的 Internet 时很有用 协议,因为标准通常规定“\r\n” 线路终止器,但不合格的客户有时会说只是 “\n”。

  • EVBUFFER_EOL_ANY

    行尾是任意数量的回车符的任意序列 和换行符。这种格式不是很有用;它 主要是为了向后兼容而存在的。

  • EVBUFFER_EOL_NUL

    行尾是值为 0 的单个字节,即 一个 ASCII NUL。

(请注意,如果您使用 event_set_mem_functions() 来覆盖 默认 malloc,evbuffer_readln 返回的字符串将是 由您指定的 malloc-replacement 分配。

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() 接口在 Libevent 1.4.14-stable 和 后。EVBUFFER_EOL_NUL是在 Libevent 2.1.1-alpha 中添加的。

在 evbuffer 中搜索

evbuffer_ptr结构指向 evbuffer 中的一个位置, 并包含可用于遍历 EVBUFFER 的数据。

接口

struct evbuffer_ptr {
ev_ssize_t pos;
struct {
/* internal fields */
} _internal;
};

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() 函数扫描缓冲区以查找出现 len-character 字符串 what。它返回evbuffer_ptr一个包含 字符串的位置,如果未找到字符串,则为 -1。如果提供了 start 参数,则它是搜索的位置 应该开始;否则,搜索将从字符串的开头开始。

evbuffer_search_range() 函数的行为与evbuffer_search相同,不同之处在于它只考虑what在start和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。如果 how是EVBUFFER_PTR_SET, 指针将移动到缓冲区内的绝对位置。 如果how是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 值失效,并使其不安全。

这些接口是 Libevent 2.0.1-alpha 中的新功能。

在不复制数据的情况下检查数据

有时,您希望在 evbuffer 中读取数据而不将其复制出来(如 evbuffer_copyout() ),并且没有重新排列 evbuffer 的内部 内存(如 evbuffer_pullup() )。有时您可能希望在 evbuffer 的中间。

您可以通过以下方式执行此操作:

接口

struct evbuffer_iovec {
void *iov_base;
size_t iov_len;
}; //返回值未需要的数据块(evbuffer_iovec)的数量
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() 时,你给它一个 evbuffer_iovec 数组 vec_out结构。数组的长度为 n_vec。它设置了这些 结构,以便每个结构都包含指向 evbuffer 块的指针 内部 RAM (iov_base),以及其中设置的内存长度 块。

如果 len 小于 0,则 evbuffer_peek() 尝试填充所有 您给它evbuffer_iovec结构。否则,它会填充它们,直到 要么它们都被使用,要么至少 len 字节是可见的。如果 函数可以给你所有你要求的数据,它返回 evbuffer_iovec它实际使用的结构。否则,它将返回 它需要的数字才能满足您的要求。

ptr 为 NULL 时,evbuffer_peek() 从缓冲区的开头开始。 否则,它从 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() 为您提供的范围后。

此函数是 Libevent 2.0.2-alpha 中的新功能。

直接将数据添加到 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);

evbuffer_reserve_space() 函数为您提供指向内部空间的指针 evbuffer。它会根据需要扩展缓冲区,以提供最小大小的字节。指向这些范围及其长度的指针将是 存储在您使用 VEC 传入的向量数组中;n_vec是 此数组的长度。

n_vec 的值必须至少为 1。如果您只提供一个 vector,则 Libevent 将确保您拥有所有连续空间 您在单个范围中请求,但可能需要重新排列 缓冲或浪费内存以执行此操作。为了获得更好的性能, 提供至少 2 个向量。该函数返回提供的数量 它需要的矢量,用于您请求的空间。

写入这些向量的数据不是缓冲区的一部分 直到你调用 evbuffer_commit_space(),它实际上使数据 你把计数写成在缓冲区中。如果要提交更少的空间 比您要求的要少,您可以iov_len减少任何 evbuffer_iovec给你的结构。您也可以减少传回 向量比你得到的要多。evbuffer_commit_space() 函数 成功时返回 0,失败时返回 -1。

注意事项

  • 调用任何重新排列 evbuffer 或向其添加数据的函数 evbuffer 将使您从中获得的指针失效 evbuffer_reserve_space()。
  • 在当前的实现中,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;
n_to_add -= 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're
lucky */
evbuffer_commit_space(buf, v, 1);
}

自 Libevent 以来,这些函数一直存在于其现有接口中 2.0.2-阿尔法。

带 evbuffers 的网络 IO

Libevent 中 evbuffers 最常见的用例是网络 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() 函数从 套接字 fdbuffer末尾。它返回读取的字节数 成功,EOF 为 0,错误为 -1。请注意,该错误可能 指示非阻塞操作不会成功;你需要 检查 EAGAIN(或 Windows 上的 WSAEWOULDBLOCK)的错误代码。 如果多少是负数,evbuffer_read() 会尝试猜测多少 读取自身。

evbuffer_write_atmost() 函数尝试将buffer 前面howmuch字节写入套接字 fd。它返回一个 成功时写入的字节数,失败时写入的字节数为 -1。与 evbuffer_read(),您需要检查错误代码,看看 error 是真实的,或者只是表示非阻塞 IO 不能 立即完成。如果你给howmuch一个负值, 我们尝试写入缓冲区的全部内容。

调用 evbuffer_write() 与调用带有负数的 howmuch 参数evbuffer_write_atmost()函数功能相同:它将尝试尽可能多地刷新缓冲区。

在 Unix 上,这些函数应该适用于任何文件描述符 支持读写。在 Windows 上,仅支持套接字。

请注意,当您使用 bufferevents 时,您不需要调用 这些 IO 功能;BufferEvents 代码会为您完成此操作。

evbuffer_write_atmost() 函数是在 Libevent 2.0.1-alpha 中引入的。

Evbuffers 和回调

evbuffers 的用户经常想知道何时将数据添加到或 从 evbuffer 中删除。为了支持这一点,Libevent 提供了一个 通用 EVPuamp 回调机制。

接口

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 。它接收缓冲区,指向 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删除回调 您添加了它,或者通过您使用的回调和指针添加了它。这 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 发生更改时,立即运行,而是作为给定事件库的事件循环的一部分延迟运行。 如果您有多个 evbuffers 的回调,这可能会有所帮助 可能导致数据相互添加和删除,以及 你要避免砸碎堆栈。

如果 evbuffer 的回调被延迟,那么当它们最终被推迟时 调用时,它们可以汇总多个操作的结果。

与 bufferevents 一样,evbuffers 在内部进行引用计数,因此 释放 evbuffer 是安全的,即使它有延迟的回调 尚未执行。

整个回调系统在 Libevent 2.0.1-alpha 中是新的。这 evbuffer_cb_(set|clear)_flags() 函数已经存在,它们的 自 2.0.2-alpha 以来的现有接口。

使用基于 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 只存储一个 指向存储在 Data 中的 datlen 字节的指针。因此, 只要 EVBUFFER 正在使用指针,指针就必须保持有效。 当 evbuffer 不再需要数据时,它会调用提供的 “cleanupfn”函数,带有提供的“data”指针,“datlen”值, 和“额外”指针作为参数。 此函数在成功时返回 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() 函数已存在 接口从 2.0.2-alpha 开始。

将文件添加到 evbuffer

某些操作系统提供了将文件写入网络的方法 而无需将数据复制到用户空间。您可以访问这些 机制(如果可用),具有简单的界面:

接口

int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
size_t length);

evbuffer_add_file() 函数假定它有一个打开的文件 描述符(不是套接字,这一次!可用于的 FD 读数。它将文件的长度字节(从位置偏移量开始)添加到输出末尾。成功时返回 0,或返回 -1 失败。

警告

在 Libevent 2.0.x 中,唯一可靠的数据 添加这种方式是用 evbuffer_write() 将其发送到网络, 用 evbuffer_drain() 排空它,或用 evbuffer__buffer()。您无法可靠地从缓冲区中提取它 用 evbuffer_remove(),用 evbuffer_pullup() 线性化,依此类推 上。Libevent 2.1.x 尝试修复此限制。

如果您的操作系统支持 splice() 或 sendfile(),则 Libevent 调用时会使用它直接将数据从FD发送到网络 evbuffer_write(),则完全没有将数据复制到用户RAM。如果 splice/sendfile 不存在,但你有 mmap(),Libevent 会 mmap 文件和你的内核有望发现它永远不需要 将数据复制到用户空间。否则,Libevent 将只读取 数据从磁盘到RAM。

从 evbuffer,或者当 evbuffer 被释放时。如果这不是你想要的,或者如果 您想要对文件进行更精细的控制,请参阅file_segment 功能如下。

此函数是在 Libevent 2.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 中。上 错误,则返回 NULL。

文件段使用 sendfile、splice、mmap、CreateFileMapping、 或 malloc()-and-read(),视情况而定。它们是使用最多的 轻量级支撑机构,并过渡到重量较重的机构 根据需要。(例如,如果您的操作系统支持 sendfile 和 mmap,则文件 segment 只能使用 sendfile 实现,直到您尝试实际 检查其内容。在这一点上,它需要 mmap()ed.)您可以 使用以下标志控制文件段的细粒度行为:

  • EVBUF_FS_CLOSE_ON_FREE

    如果设置了此标志,则释放文件段 evbuffer_file_segment_free() 将关闭基础文件。

  • EVBUF_FS_DISABLE_MMAP

    如果设置了此标志,则file_segment将永远不会使用映射内存 style 后端 (CreateFileMapping, mmap) 来表示,即使这样 要适当。

  • EVBUF_FS_DISABLE_SENDFILE

    如果设置了此标志,则file_segment将永远不会使用 sendfile 样式 此文件的后端(sendfile、splice),即使会 要适当。

  • EVBUF_FS_DISABLE_LOCKING

    如果设置了此标志,则不会为文件段分配任何锁:它 以任何可以被多人看到的方式使用它都是不安全的 线程。

一旦你有了evbuffer_file_segment,你可以将部分或全部添加到一个 evbuffer 使用 evbuffer_add_file_segment()。此处的 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);

您可以将回调函数添加到将在以下情况下调用的文件段 对文件段的最终引用已发布,并且文件 段即将被释放。此回调不得尝试恢复 文件段,将其添加到任何缓冲区,依此类推。

这些文件段函数最早出现在 Libevent 2.1.1-alpha 中; evbuffer_file_segment_add_cleanup_cb() 是在 2.1.2-alpha 中添加的。

通过引用将一个 evbuffer 添加到另一个 evbuffer

您还可以通过引用将一个 evbuffer 添加到另一个 evbuffer:而不是删除 一个缓冲区的内容并将它们添加到另一个缓冲区,您得到一个 evbuffer 对另一个的引用,它的行为就好像你已经复制了所有的 字节。

接口

int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
struct evbuffer *inbuf);

evbuffer_add_buffer_reference() 函数的行为就像您复制了一样 从 outbufinbuf 的所有数据,但不执行任何不必要的操作 副本。如果成功,则返回 0,失败时返回 -1。

请注意,对 inbuf 内容的后续更改不会反映在 outbuf 中:此函数通过以下方式添加 evbuffer 的当前内容 引用,而不是 EVbuffer 本身。

另请注意,您不能嵌套缓冲区引用:已 作为一个evbuffer_add_buffer_reference的输出,不能成为另一个呼叫的输出

此函数是在 Libevent 2.1.1-alpha 中引入的。

使 evbuffer 仅能添加或删除

接口

int evbuffer_freeze(struct evbuffer *buf, int at_front);
int evbuffer_unfreeze(struct evbuffer *buf, int at_front);

您可以使用这些函数暂时禁用对 evbuffer 头部和尾部的修改。bufferevent 代码使用它们 内部以防止意外修改前部 输出缓冲区或输入缓冲区的末尾。

evbuffer_freeze() 函数是在 Libevent 中引入的 2.0.1-阿尔法。

过时的 evbuffer 函数

evbuffer 接口在 Libevent 2.0 中发生了很大变化。在此之前, 每个 evbuffers 都作为连续的 RAM 块实现,它 使访问效率非常低下。

event.h 标头用于公开 struct 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() 函数将搜索第一次出现的 缓冲区中的字符串,并返回指向它的指针。与 evbuffer_search(),它只能找到第一个字符串。留下来 与使用此函数的旧代码兼容,它现在线性化 整个缓冲区,直到找到的字符串的末尾。

回调接口也不同:

已弃用的接口

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 一次只能设置一个回调,因此设置一个 new callback 将禁用之前的回调,并将 NULL 的回调是禁用回调的首选方法。

该函数不是获得evbuffer_cb_info_structure,而是 使用 evbuffer 的旧长度和新长度调用。因此,如果old_len 大于 new_len,数据被耗尽。如果new_len更大 比old_len,添加了数据。无法推迟回调, 因此,添加和删除从未被批处理到单个回调中 调用。

此处的过时函数在 event2/buffer_compat.h 中仍然可用。

源文档地址:https://libevent.org/libevent-book/Ref7_evbuffer.html

libevent之evbuffer的更多相关文章

  1. libevent (二) 接收TCP连接

    libevent 接收TCP连接 Evconnlistener 机制为您提供了侦听和接受传入的 TCP 连接的方法.下面的函数全部包含在`<event2/listener.h>`中. ev ...

  2. libevent库介绍--事件和数据缓冲

    首先在学习libevent库的使用前,我们还要从基本的了解开始,已经熟悉了epoll以及reactor,然后从event_base学习,依次学习事件event.数据缓冲Bufferevent和数据封装 ...

  3. Libevent:9Evbuffers缓存IO的实用功能

    Libevent的evbuffer功能实现了一个字节队列,优化了在队列尾端增加数据,以及从队列前端删除数据的操作. Evbuffer用来实现缓存网络IO中的缓存部分.它们不能用来在条件发生时调度IO或 ...

  4. Envoy 源码分析--buffer

    目录 Envoy 源码分析--buffer BufferFragment RawSlice Slice OwnedSlice SliceDeque UnownedSlice OwnedImpl Wat ...

  5. libevent evbuffer bug

    今天发现 libevent 2.0.22 一个坑爹的bug,导致消息混乱.查找问题浪费一天,复现代码如下 #include <event2/buffer.h> #include <s ...

  6. libevent学习八(evbuffer)

    1.evbuffer以队列的形式管理字节,从尾部添加,从头部取出(FIFO) 2.evbuffer内部存储形式是多个独立的连续内存       接口 //创建和删除 struct evbuffer * ...

  7. libevent笔记3:evbuffer

    evbuffer 之前提到bufferevent结构体提供两个缓存区用来为读写提供缓存,并自动进行IO操作.这两个缓存区是使用Libevent中的evbuffer实现的,同样,Libevent中也提供 ...

  8. libevent evbuffer参考文章

    https://blog.csdn.net/FreeeLinux/article/details/52799951 http://cache.baiducontent.com/c?m=9d78d513 ...

  9. Libevent初探

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

  10. 基于Libevent的HTTP Server

    简单的Http Server 使用Libevent内置的http相关接口,可以很容易的构建一个Http Server,一个简单的Http Server如下: #include <event2/e ...

随机推荐

  1. pnpm的基本原理及快速使用

    基本原理 前置知识:软件链接与硬链接 软链接(符号链接Symbolic link):是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用.在window快捷方式上和其 ...

  2. Nats集群部署

    环境: 3台机器采用同样的目录名字和文件名称 服务器 192.168.10.30 192.168.10.31 192.168.10.32 nats版本2.9.15 配置文件 # 192.168.10. ...

  3. K8s控制器(8)---Deployment

    一.Deployment控制器概念.原理解读 1.1 Deployment概述 # Deployment官方文档 https://kubernetes.io/docs/concepts/workloa ...

  4. 深入学习和理解Django视图层:处理请求与响应

    title: 深入学习和理解Django视图层:处理请求与响应 date: 2024/5/4 17:47:55 updated: 2024/5/4 17:47:55 categories: 后端开发 ...

  5. JDK源码阅读-------自学笔记(二十一)(java.util.ArrayList详细版集合类)

    一.前景提要 本人经历了IT行业的多种类型企业,外包/创业小公司/中型公司,一步步成长起来,现在可以给大家透露下为什么进大企业在IT行业是重要的: 在外包公司,你要做的就是对接别人写好的接口,然后按照 ...

  6. ETSI GS MEC 015,MEP 带宽管理 API

    目录 文章目录 目录 版本 ETSI MEC 对 MEP 带宽管理功能的定义 功能理解 Bandwidth Management Service BWMS UML ME APP registers t ...

  7. 4G EPS 中的消息类型

    目录 文章目录 目录 消息 MIB(主消息块) SIBs(多个系统消息块) 系统消息的映射和调度 系统信息的更改通知 消息 LTE 的系统消息是蜂窝网络与 UE 互相交互的与 LTE 系统相关的.特殊 ...

  8. 整理C语言预处理过程语法的实用方法与技巧

    预处理 目录 预处理 一.宏定义 数值宏常量 字符串宏常量 用define宏定义注释符号? 程序的编译过程 预处理中宏替换和去注释谁先谁后? 如何写一个不会出现问题的宏函数 do-while-zero ...

  9. IceRPC之调度管道->快乐的RPC

    作者引言 很高兴啊,我们来到了IceRPC之调度管道->快乐的RPC, 基础引导,有点小压力,打好基础,才能让自已不在迷茫,快乐的畅游世界. 调度管道 Dispatch pipeline 了解如 ...

  10. phpstorm对laravel开发的配置

    摘自:https://www.cnblogs.com/Richard-Tang/p/10218178.html phpstorm对laravel开发的配置   一.安装Laravel 1.下载comp ...