目次
Bufferevents:概念和基础知识
大多数情况下,应用程序希望执行一定数目的数据 除了仅响应事件之外,还进行缓冲。当我们想要的时候 写入数据,比方,通常的模式运行如下:
- 决定我们要将一些数据写入连接;把那个 缓冲区中的数据。
- 等候连接变为可写
- 尽可能多地写入数据
- 记住我们写了多少,假如我们另有更多的数据要写, 等候连接再次变为可写。
这种缓冲 IO 模式很常见,以至于 Libevent 提供了一个 它的通用机制。“缓冲事件”由 底层传输(如套接字)、读取缓冲区和写入 缓冲区。而不是常规事件,这些事件在 基础传输已准备好读取或写入,即 BufferEvent 在读取或写入足够多的内容时调用其用户提供的回调 数据。
有多种类型的缓冲事件,它们都共享一个共同点 接口。在撰写本文时,存在以下类型:
- 基于套接字的缓冲事件
从基础发送和接收数据的缓冲事件 stream socket,使用 event_* 接口作为厥后端。
- 异步 IO 缓冲区事件
使用 Windows IOCP 接口发送和 将数据接收到基础流套接字。(仅限 Windows; 实行性的。
- 筛选缓冲区事件
之前处理传入和传出数据的缓冲事件 将其传递给基础 BufferEvent 对象,比方,传递给 压缩或翻译数据。
- 配对缓冲事件
两个相互传输数据的缓冲事件。
注意
从 Libevent 2.0.2-alpha 开始,这里的 bufferevents 接口仍然是 在所有 BufferEvent 类型中不完全正交。换言之, 并非下面描述的每个接口都实用于所有 BufferEvent 类型。 Libevent 开发人员打算在未来的版本中改正此问题。
另请注意
缓冲区事件目前仅实用于面向流的协议,如 TCP。 将来可能会支持面向数据报的协议,如 UDP。
本节中的所有函数和类型都在 event2/bufferevent.h文件。与 evbuffers 特殊相干的函数包括 在 event2/buffer.h 中声明;有关以下方面的信息,请参阅下一章 那些。
Bufferevents 和 evbuffers
每个缓冲区事件都有一个输入缓冲区和一个输出缓冲区。这些是 类型为“struct evbuffer”。当您有数据要写入时 buffer事件,将其添加到输出缓冲区;当 bufferevent 具有 数据供您读取,请将其从输入缓冲区中排出。
evbuffer 接口支持多种操作;我们讨论它们 后面的部分。
回调和水印
每个 bufferevent 都有两个与数据相干的回调:一个读取回调 和写入回调。默认情况下,读取回调被调用 每当从基础传输中读取任何数据时,写入 每当输出缓冲区中的足够数据被清空时,就会调用回调 基础传输。您可以覆盖这些函数的行为 通过调整 bufferevent 的读写“水印”。
每个缓冲事件都有四个水印:
- 读取低水位线
每当发生离开 bufferevent 的输入缓冲区的读取时 在此级别或更高级别,将调用 BufferEvent 的 Read 回调。 默认为 0,因此每次读取都会产生读取回调被调用。
- 读取高水位线
假如 bufferevent 的输入缓冲区达到此级别,则 buffer事件制止读取,直到从输缓冲区,再次将我们带到它下方。默认为无限制,所以 我们永久不会因为输入缓冲区的大小而制止读取。
- 写下低水位线
每当发生将我们带到此级别或更低级别的写入时,我们 调用写入回调。默认值为 0,因此写入回调 除非清空输出缓冲区,否则不会调用。
- 写高水位线
不直接由缓冲区事件使用,此水印可以具有特殊的 这意味着当 bufferevent 用作 另一个 BufferEvent。请参阅下面有关筛选缓冲区事件的说明。
bufferevent 还具有 “error” 或 “event” 回调,该回调将 调用以告知应用程序有关非面向数据的事件,比方 当连接关闭或发生错误时。以下事件 标志的定义如下:
- BEV_EVENT_READING
在对 bufferevent 执行读取操作期间发生事件。看 其他标志是哪个事件。
- BEV_EVENT_WRITING
在对 bufferevent 执行写入操作期间发生事件。看 其他标志是哪个事件。
- BEV_EVENT_ERROR
bufferevent 操作期间发生错误。查看更多 有关错误的信息,请调用 EVUTIL_SOCKET_ERROR()。
- BEV_EVENT_TIMEOUT
缓冲事件的超时已过期。
- BEV_EVENT_EOF
我们在 bufferevent 上得到了文件结束指示。
- BEV_EVENT_CONNECTED
我们在 bufferevent 上完成了哀求的连接。
(上述事件名称在 Libevent 2.0.2-alpha 中是新增的。
耽误回调
默认情况下,在以下情况下会立即执行 bufferevent 回调 发生相应的情况。(evbuffer 也是如此 回调也是如此;我们稍后谈判到这些。此即时调用 当依赖关系变得复杂时,可能会造成麻烦。比方,假设 有一个回调,当数据增长时,它会将数据移动到 evbuffer A 中 empty,以及另一个从 evbuffer A 处理数据的回调 它长得很饱满。由于这些调用都发生在堆栈上,因此 假如依赖项变得足够讨厌,则可能碰面对堆栈溢出的风险。
为相识决这个问题,你可以告诉 bufferevent(或 evbuffer)它的 回调应该被推迟。当满足条件时 耽误回调,而不是立即调用它,而是排队 作为 event_loop() 调用的一部分,并在常规事件之后调用。 回调。
(耽误回调是在 Libevent 2.0.1-alpha 中引入的。
缓冲区事件的选项标志
在创建 bufferevent 时,可以使用一个或多个标志来更改其 行为。识别的标志包括:
- BEV_OPT_CLOSE_ON_FREE
开释缓冲事件后,关闭基础传输。 这将关闭底层套接字,开释底层 bufferevent 等。
- BEV_OPT_THREADSAFE
主动为 bufferevent 分配锁,以便它 可从多个线程安全使用。
- BEV_OPT_DEFER_CALLBACKS
设置此标志后,bufferevent 会耽误其所有回调, 如上所述。
- BEV_OPT_UNLOCK_CALLBACKS
默认情况下,当 bufferevent 设置为线程安全时, 每当任何用户提供 调用回调。设置此选项可使 Libevent 发布 bufferEvent 调用回调时的锁定。
(Libevent 2.0.5-beta 于 BEV_OPT_UNLOCK_CALLBACKS 年推出。其他选项 以上是 Libevent 2.0.1-alpha 中的新功能。
使用基于套接字的缓冲区事件
要使用的最简朴的缓冲区事件是基于套接字的类型。一个 基于 socket 的 bufferevent 使用 Libevent 的底层事件机制来 检测底层网络套接字何时准备好读取和/或写入 操作,并使用底层网络调用(如 readv、writev、 WSASend 或 WSARecv) 来传输和接收数据。
创建基于套接字的缓冲区事件
可以使用以下方法创建基于套接字的缓冲区事件 bufferevent_socket_new():
接口- struct bufferevent *bufferevent_socket_new(
- struct event_base *base,
- evutil_socket_t fd,
- enum bufferevent_options options);
复制代码 base 是 event_base,options 是 bufferevent 的位掩码 选项(BEV_OPT_CLOSE_ON_FREE等)。fd 参数是一个 套接字的可选文件描述符。假如出现以下情况,您可以将 fd 设置为 -1 您希望稍后设置文件描述符。
提示[确保您提供给bufferevent_socket_new的套接字是 在非阻塞模式下。Libevent 提供了方便的方法 evutil_make_socket_nonblocking为此。此函数在成功时返回 bufferevent,在失败时返回 NULL。
bufferevent_socket_new() 函数是在 Libevent 2.0.1-alpha 中引入的。
在基于套接字的缓冲区事件上启动连接
假如 bufferevent 的套接字尚未连接,则可以启动新的 连接。
接口- int bufferevent_socket_connect(struct bufferevent *bev,
- struct sockaddr *address, int addrlen);
复制代码 address 和 addrlen 参数与尺度调用雷同 connect() 中。假如 bufferevent 尚未设置套接字, 调用此函数会为其分配一个新的流套接字,并使 它不阻塞。
假如 bufferevent 确实已经有套接字,则调用 bufferevent_socket_connect() 告诉 Libevent 套接字不是 连接,并且在套接字上不执行任何读取或写入操作,直到 连接操作已成功。
在连接之前将数据添加到输出缓冲区是可以的 做。
假如连接已成功启动,则此函数返回 0,并且 -1 假如发生错误。
例- #include <event2/event.h>
- #include <event2/bufferevent.h>
- #include <sys/socket.h>
- #include <string.h>
- void eventcb(struct bufferevent *bev, short events, void *ptr)
- {
- if (events & BEV_EVENT_CONNECTED) {
- /* We're connected to 127.0.0.1:8080. Ordinarily we'd do
- something here, like start reading or writing. */
- } else if (events & BEV_EVENT_ERROR) {
- /* An error occured while connecting. */
- }
- }
- int main_loop(void)
- {
- struct event_base *base;
- struct bufferevent *bev;
- struct sockaddr_in sin;
- base = event_base_new();
- memset(&sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
- sin.sin_port = htons(8080); /* Port 8080 */
- bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
- bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
- if (bufferevent_socket_connect(bev,
- (struct sockaddr *)&sin, sizeof(sin)) < 0) {
- /* Error starting connection */
- bufferevent_free(bev);
- return -1;
- }
- event_base_dispatch(base);
- return 0;
- }
复制代码 bufferevent_socket_connect() 函数是在 libevent-2.0.2-alpha。 在此之前,您必须手动调用 connect() 在您的套接字上,以及连接的时间 完成后,BufferEvent 会将其报告为写入。
请注意BEV_EVENT_CONNECTED,只有在启动 使用 bufferevent_socket_connect() 尝试 connect()。假如您调用 connect() 时,连接会报告为写入。
假如您想自己调用 connect(),但仍然收到一个 BEV_EVENT_CONNECTED 事件 连接成功时,调用 bufferevent_socket_connect(bev, NULL, 0) 后 connect() 返回 -1 和 errno 即是 EAGAIN 或 EINPROGRESS。
此函数是在 Libevent 2.0.2-alpha 中引入的。
按主机名启动连接
许多时候,您希望将剖析主机名和连接到主机名结合起来 变成一个操作。有一个接口:
接口- int bufferevent_socket_connect_hostname(struct bufferevent *bev,
- struct evdns_base *dns_base, int family, const char *hostname,
- int port);
- int bufferevent_socket_get_dns_error(struct bufferevent *bev);
复制代码 此函数剖析 DNS 名称主机名,查找 family 类型的地点。(允许的家庭类型为 AF_INET、AF_INET6 和 AF_UNSPEC。假如 名称剖析失败,它会使用错误事件调用事件回调。 假如成功,它会像bufferevent_connect一样启动连接尝试 愿意。
dns_base 参数是可选的。假如它为 NULL,则 Libevent 会阻塞 等候名称查找完成,这通常不是您想要的。假如 它提供了,然后 Libevent 使用它异步查找主机名。 有关 DNS 的更多信息,请参阅第 R9 章。
与 bufferevent_socket_connect() 一样,此函数告诉 Libevent 任何 BufferEvent 上的现有套接字未连接,并且没有读取或写入 应该在套接字上完成,直到剖析完成并连接 操作成功。
假如发生错误,则可能是 DNS 主机名查找错误。你可以找到 通过调用找出最近的错误是什么 bufferevent_socket_get_dns_error()。假如返回的错误代码为 0,则没有 DNS 检测到错误。
示例:简朴的 HTTP v0 客户端。- /* Don't actually copy this code: it is a poor way to implement an
- HTTP client. Have a look at evhttp instead.
- */
- #include <event2/dns.h>
- #include <event2/bufferevent.h>
- #include <event2/buffer.h>
- #include <event2/util.h>
- #include <event2/event.h>
- #include <stdio.h>
- void readcb(struct bufferevent *bev, void *ptr)
- {
- char buf[1024];
- int n;
- struct evbuffer *input = bufferevent_get_input(bev);
- while ((n = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
- fwrite(buf, 1, n, stdout);
- }
- }
- void eventcb(struct bufferevent *bev, short events, void *ptr)
- {
- if (events & BEV_EVENT_CONNECTED) {
- printf("Connect okay.\n");
- } else if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) {
- struct event_base *base = ptr;
- if (events & BEV_EVENT_ERROR) {
- int err = bufferevent_socket_get_dns_error(bev);
- if (err)
- printf("DNS error: %s\n", evutil_gai_strerror(err));
- }
- printf("Closing\n");
- bufferevent_free(bev);
- event_base_loopexit(base, NULL);
- }
- }
- int main(int argc, char **argv)
- {
- struct event_base *base;
- struct evdns_base *dns_base;
- struct bufferevent *bev;
- if (argc != 3) {
- printf("Trivial HTTP 0.x client\n"
- "Syntax: %s [hostname] [resource]\n"
- "Example: %s www.google.com /\n",argv[0],argv[0]);
- return 1;
- }
- base = event_base_new();
- dns_base = evdns_base_new(base, 1);
- bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
- bufferevent_setcb(bev, readcb, NULL, eventcb, base);
- bufferevent_enable(bev, EV_READ|EV_WRITE);
- evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n", argv[2]);
- bufferevent_socket_connect_hostname(
- bev, dns_base, AF_UNSPEC, argv[1], 80);
- event_base_dispatch(base);
- return 0;
- }
复制代码 bufferevent_socket_connect_hostname() 函数是 Libevent 中的新功能 2.0.3-阿尔法;bufferevent_socket_get_dns_error() 是 2.0.5-beta 中的新功能。
通用 bufferevent 操作
本节中的函数实用于多个缓冲区事件 实现。
开释缓冲区事件
接口- void bufferevent_free(struct bufferevent *bev);
复制代码 此函数开释缓冲区事件。缓冲区事件在内部 reference-counted,因此假如 bufferevent 有待处理的耽误 回调 当你开释它时,它不会被删除,直到回调 都完成了。
但是,bufferevent_free() 函数会尝试开释 Buffer事件。假如有待处理的数据要写入 BufferEvent,它可能不会在 BufferEvent 之前刷新 开释。
假如设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且此 bufferevent 具有 套接字或与之关联的底层缓冲事件作为其传输, 开释 BufferEvent 时,该传输将关闭。
此函数是在 Libevent 0.8 中引入的。
操作回调、水印和启用的操作
接口- typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
- typedef void (*bufferevent_event_cb)(struct bufferevent *bev,
- short events, void *ctx);
- void bufferevent_setcb(struct bufferevent *bufev,
- bufferevent_data_cb readcb, bufferevent_data_cb writecb,
- bufferevent_event_cb eventcb, void *cbarg);
- void bufferevent_getcb(struct bufferevent *bufev,
- bufferevent_data_cb *readcb_ptr,
- bufferevent_data_cb *writecb_ptr,
- bufferevent_event_cb *eventcb_ptr,
- void **cbarg_ptr);
复制代码 bufferevent_setcb() 函数更改一个或多个回调 缓冲事件。readcb、writecb 和 eventcb 函数是 当读取足够多的数据时,调用(分别),当读取足够的数据时 写入,或事件发生时。每个参数的第一个参数是 发生事件的 bufferEvent。最后一个参数是 用户在 cbarg 参数中提供的值 bufferevent_callcb():你可以用它来将数据传递给你的 回调。事件回调的 events 参数是一个位掩码 事件标志:请参阅上面的“回调和水印”。
您可以通过传递 NULL 而不是回调来禁用回调 功能。请注意,bufferevent 上的所有回调函数都共享 单个 cbarg 值,因此更改它将影响所有值。
您可以通过传送 bufferevent 来检索当前设置的回调 指向 bufferevent_getcb() 的指针,该指针将 readcb_ptr 设置为当前读取 回调,writecb_ptr当前写入回调,*eventcb_ptr 当前事件回调,以及 *cbarg_ptr 当前回调参数 田。任何设置为 NULL 的指针都将被忽略。
bufferevent_setcb() 函数是在 Libevent 1.4.4 中引入的。类型 名称“bufferevent_data_cb”和“bufferevent_event_cb”在 Libevent 中是新的 2.0.2-阿尔法。bufferevent_getcb() 函数是在 2.1.1-alpha 中添加的。
接口- void bufferevent_enable(struct bufferevent *bufev, short events);
- void bufferevent_disable(struct bufferevent *bufev, short events);
- short bufferevent_get_enabled(struct bufferevent *bufev);
复制代码 您可以启用或禁用事件 EV_READ、EV_WRITE 或 EV_READ|EV_WRITE在缓冲事件上。当阅读或写作不是 启用后,BufferEvent 将不会尝试读取或写入数据。
当输出缓冲区为空时,无需禁用写入: BufferEvent 主动制止写入,然后再次重新启动 当有数据要写入时。
同样,当输入缓冲区 达到其高水位线:缓冲事件主动制止 读取,并在有阅读空间时重新启动。
默认情况下,新创建的 bufferevent 已启用写入功能,但未启用 读数。
您可以调用 bufferevent_get_enabled() 来查看当前正在发生的事件 在 BufferEvent 上启用。
这些函数是在 Libevent 0.8 中引入的,但 bufferevent_get_enabled(),在 2.0.3-alpha 版本中引入。
接口- void bufferevent_setwatermark(struct bufferevent *bufev, short events,
- size_t lowmark, size_t highmark);
复制代码 bufferevent_setwatermark() 函数调整读取水印, 写入单个 BufferEvent 的水印,或同时写入水印。(假如EV_READ 在事件字段中设置,则调整读取的水印。假如 EV_WRITE在事件字段中设置,则会调整写入水印。
高水位线 0 等同于“无限”。
此函数在 Libevent 1.4.4 中首次公开。
例- #include <event2/event.h>
- #include <event2/bufferevent.h>
- #include <event2/buffer.h>
- #include <event2/util.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <string.h>
- struct info {
- const char *name;
- size_t total_drained;
- };
- void read_callback(struct bufferevent *bev, void *ctx)
- {
- struct info *inf = ctx;
- struct evbuffer *input = bufferevent_get_input(bev);
- size_t len = evbuffer_get_length(input);
- if (len) {
- inf->total_drained += len;
- evbuffer_drain(input, len);
- printf("Drained %lu bytes from %s\n",
- (unsigned long) len, inf->name);
- }
- }
- void event_callback(struct bufferevent *bev, short events, void *ctx)
- {
- struct info *inf = ctx;
- struct evbuffer *input = bufferevent_get_input(bev);
- int finished = 0;
- if (events & BEV_EVENT_EOF) {
- size_t len = evbuffer_get_length(input);
- printf("Got a close from %s. We drained %lu bytes from it, "
- "and have %lu left.\n", inf->name,
- (unsigned long)inf->total_drained, (unsigned long)len);
- finished = 1;
- }
- if (events & BEV_EVENT_ERROR) {
- printf("Got an error from %s: %s\n",
- inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
- finished = 1;
- }
- if (finished) {
- free(ctx);
- bufferevent_free(bev);
- }
- }
- struct bufferevent *setup_bufferevent(void)
- {
- struct bufferevent *b1 = NULL;
- struct info *info1;
- info1 = malloc(sizeof(struct info));
- info1->name = "buffer 1";
- info1->total_drained = 0;
- /* ... Here we should set up the bufferevent and make sure it gets
- connected... */
- /* Trigger the read callback only whenever there is at least 128 bytes
- of data in the buffer. */
- bufferevent_setwatermark(b1, EV_READ, 128, 0);
- bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);
- bufferevent_enable(b1, EV_READ); /* Start reading. */
- return b1;
- }
复制代码 操作缓冲区事件中的数据
从网络读取和写入数据对您没有长处,假如您不能看它。Bufferevents 为您提供了这些方法来提供它们 要写入的数据,以及要读取的数据:
接口- struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
- struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
复制代码 这两个函数是非常强大的基本函数:它们返回 分别输入和输出缓冲器。有关所有的完整信息 可以对 EVPud 类型执行的操作,请参见下一篇 章。
请注意,应用程序只能从输入中删除(而不是添加)数据 缓冲区,并且只能向输出缓冲区添加(而不是删除)数据。
假如写入缓冲事件由于数据太少而制止 (大概假如读取因太多而故步自封),然后将数据添加到 输出缓冲区(或从输入缓冲区中删除数据)将 主动重新启动它。
这些函数是在 Libevent 2.0.1-alpha 中引入的。
接口- int bufferevent_write(struct bufferevent *bufev,
- const void *data, size_t size);
- int bufferevent_write_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
复制代码 这些函数将数据添加到 bufferevent 的输出缓冲区。叫 bufferevent_write() 将内存中的**大小字节添加到 输出缓冲区的末尾。调用 bufferevent_write_buffer() 删除 BUF 的全部内容,并将它们放在输出的末尾 缓冲区。假如成功,两者都返回 0,假如发生错误,则返回 -1。
这些函数从 Libevent 0.8 开始就已经存在了。
接口- size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
- int bufferevent_read_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
复制代码 这些函数从 bufferevent 的输入缓冲区中删除数据。这 bufferevent_read() 函数从输入中删除最大size的字节 缓冲区,将它们存储在data存储器中。它返回数字 实际删除的字节数。bufferevent_read_buffer() 函数 排出输入缓冲区的全部内容并将它们放入 BUF;成功时返回 0,失败时返回 -1。
请注意,使用 bufferevent_read(),数据中的内存块必须 实际上有足够的空间来容纳大小字节的数据。
bufferevent_read() 函数自 Libevent 0.8 以来就已存在; bufferevent_read_buffer() 是在 Libevent 2.0.1-alpha 中引入的。
例
[code]#include #include #include voidread_callback_uppercase(struct bufferevent *bev, void *ctx){ /* This callback removes the data from bev's input buffer 128 bytes at a time, uppercases it, and starts sending it back. (Watch out! In practice, you shouldn't use toupper to implement a network protocol, unless you know for a fact that the current locale is the one you want to be using.) */ char tmp[128]; size_t n; int i; while (1) { n = bufferevent_read(bev, tmp, sizeof(tmp)); if (n |