翻译自:http://www.wangafu.NET/~nickm/libevent-book/Ref6_bufferevent.html
原文:http://blog.csdn.net/windeal3203/article/details/52815520
大多数时候,应用程序除了响应请求外,还需要处理数据(及其缓存)。当我们想要写数据是,通常会有以下步骤
- 决定要向连接中写入什么数据, 把这些数据放入缓存
- 等待连接可写
- 写入尽可能多的数据
记住写入了多少数据,如果还有数据没写完。等待连接再次变为可写状态。
这样的I/O缓冲方式很常见,因而libevent为此提供了一种通用机制。
“bufferevent”由一个底层传输系统(比如socket),一个读缓冲区和一个写缓冲区组成。
对于普通的events, 当底层传输系统可读或者可写时,调用回调方式; 而bufferevent提供了一种替代方式:它在已经写入、或者读出数据的时候才调用回调函数。
libevent有多种bufferevent, 它们共享通用的接口。截止至本文撰写时,已经存在以下类型:基于socket的bufferevent:
在底层流式socket上发送和接收数据,使用event_*
接口作为其后端。- 异步IO的bufferevent:
使用Windows IOCP
接口来想底层流式socket发送和接收数据。(Windows only, 实验性的) - 过滤型的bufferevent:
在数据传送到底层bufferevent对象之前,对到来和外出的数据进行前期处理的bufferevent,比如对数据进行压缩或者转换 成对的bufferevent:
注意:截止Libevent2.0.2-alpha版本,bufferevent接口还没有完全覆盖所有的bufferevent类型。换句话说,并不是下面介绍的每一个接口都能用于所有的bufferevent类型。Libevent开发者会在未来的版本中解决该问题。
还要注意:bufferevent目前仅能工作在流式协议上,比如TCP。未来可能会支持数据报协议,比如UDP。
本文所有的函数和类型都是在<event2/bufferevent.h>
文件中声明。与evbuffers相关的函数在<event2/buffer.h>
中声明,有关信息参考下一章。
1. bufferevent和evbuffers
每一个bufferevent 都有一个输入缓冲区和输出缓冲区, 这些缓冲区(buffer)都是struct evbuffer
类型。 当你有数据要写入bufferevent, 你要先把数据填入output buffer,; 当bufferevent上有数据需要读取时,则可以从input buffer中抽取出来。
evbuffer
接口支持很多操作,会在以后的章节中进行讨论。
2. 回调函数和水位数
每一个bufferevent都有两个数据相关的回到函数, 一个 读回调和一个写回调。 默认情况下,当有数据从底层传输读取时,读回调函数就会被调用; 当ouput buffer想底层传输写入足够多的数据时, 写回调函数就会被调用。
通过调整bufferevent的读取和写入“水位线”(watermarks),可以改变这些函数的默认行为。
每个bufferevent都有4个水位线:
- 读 低水位:
当bufferevent的输入缓冲区的数据量到达该水位线或者更高时,bufferevent的读回调函数就会被调用。该水位线默认为0,所以每一次读取操作都会导致读回调函数被调用。 - 读 高水位:
如果bufferevent的输入缓冲区的数据量到达该水位线时,那么bufferevent就会停止读取,直到输入缓冲区中足够多的数据被抽走,从而数据量再次低于该水位线。默认情况下该水位线是无限制的,所以从来不会因为输入缓冲区的大小而停止读取操作。 - 写 低水位:
当写操作使得输出缓冲区的数据量达到或者低于该水位线时,才调用写回调函数。默认情况下,该值为0,所以输出缓冲区被清空时才调用写回调函数。 写 高水位:
并非由bufferevent直接使用,对于bufferevent作为其他bufferevent底层传输系统的时候,该水位线才有特殊意义。所以可以参考后面的过滤型bufferevent。
bufferevent还提供了error
或者event
的回调函数,用来通知应用程序关于非数据相关的事件。比如:关闭连接或者发生错误。 为此,定义了以下event
标志:BEV_EVENT_READING:读操作期间发生了事件。具体哪个事件参见其他标志。
- BEV_EVENT_WRITING:写操作期间发生了事件。具体哪个事件参见其他标志。
- BEV_EVENT_ERROR:在bufferevent操作期间发生了错误,可以调用
EVUTIL_SOCKET_ERROR
函数来得到更多的错误信息。 - BEV_EVENT_TIMEOUT: bufferevent上发生了超时
- BEV_EVENT_EOF: bufferevent上遇到了EOF标志
- BEV_EVENT_CONNECTED:在bufferevent上请求的连接已经完成
3. 延期回调
默认情况下, bufferevent的回调函数在响应的条件满足时会立即执行(evbuffer 回调函数也是如此, 后面会讲到)。 当依赖关系比较复杂时, 这种立即执行 的机制会导致一些问题。比如说, 假设 有一个回调函数 是用于在evbuffer A为空时向其填入数据, 而另一个回调函数则在evbuffer A为满时 从中取出数据进行处理。如果所有这些调用都发生在栈上的话,在依赖关系足够复杂的时候,有栈溢出的风险。
为了解决这个问题,你可以告诉bufferevent(或evbuffer)它的回调函数应该被延迟执行。当延迟回调函数 的对应条件满足时, 延迟回调函数不会立即执行,而是加入到event_loop()
调用队列中。然后在常规的event回调之后执行。 。
4. bufferevent 的选项标志
在穿件bufferevent时,可以通过一些选项来修改它的行为:
- BEV_OPT_CLOSE_ON_FREE: 当释放bufferevent时,关闭底层的传输系统。 这将关闭底层套接字,释放底层bufferevent等。
- BEV_OPT_THREADSAFE: 自动为bufferevent分配锁, 从而在多线程中可以安全使用。
- BEV_OPT_DEFER_CALLBACKS: bufferevent会将其所有回调函数进行延迟调用设置。
- BEV_OPT_UNLOCK_CALLBACKS: 默认情况下, 当设置bufferevent为线程安全的时候,任何用户提供的回调函数调用时都会锁住bufferevent的锁, 设置改标志可以在提供的回调函数被调用时不锁住bufferevent的锁。
5. 基于socket 的bufferevent
最简单的bufferevents就是基于socket类型的bufferevent。基于socket的bufferevent使用Libevent底层event机制探测底层网络socket何时准备好读和写,而且使用底层网络调用(比如readv
,writev
,WSASend
或WSARecv
)进行传送和接受数据。
5.1 创建 基于socket 的bufferevent
使用bufferevent_socket_new()
可以创建一个基于socket的bufferevent。
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:参数是一个可选的socket文件描述符。如果希望以后再设置socket文件描述符,可以将fd置为-1。
Tip:要确保提供给bufferevent_socket_new
的socket是非阻塞模式。Libevent提供了便于使用的evutil_make_socket_nonblocking
来设置非阻塞模式。
bufferevent_socket_new
成功时返回一个bufferevent
,失败时返回NULL
。
5.2 在基于socket的bufferevent上发送连接
如果bufferevent上的socket还没有建立连接, 我们可以发送一个新的连接:
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen);
address和addrlen参数类似于标准的connect函数。如果该bufferevent尚未设置socket,则调用该函数为该bufferevent会分配一个新的流类型的socket,并且置其为非阻塞的。
如果bufferevent已经设置了一个socket,则调用数bufferevent_socket_connect
会告知Libevent该socket尚未建立连接,在建立连接成功之前,不应该在其上进行读写操作。
在建立连接成功之前,向输出缓冲区添加数据是可以的。
该函数如果在建链成功时,返回0,如果发生错误,则返回-1.
Example
#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_base_connect()
函数是在Libevent-2.0.2-alpha版本引入的,在这之前,需要手动调用connect()
函数,并且当连接建立的时候,bufferevent会将其作为写事件进行报告。
注意:如果使用bufferevent_socket_connect
进行建链的话,会得到BEV_EVENT_CONNECTED
事件。如果自己手动调用connect()
,则会得到write
事件。
如果在手动调用connect()
的情况下,仍然想在建链成功的时候得到BEV_EVENT_CONNECTED
事件,可以在connect()
返回-1,并且errno
为EAGAIN
或EINPROGRESS
之后,调用bufferevent_socket_connect(bev, NULL, 0)
函数。
5.3 通过hostname发射连接
经常性的,你可能希望将解析主机名和建立连接操作合成一个单独的操作,这时可以使用下面的接口:
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名字hostname
,查找family
类型的地址(family
的类型可以是AF_INET
, AF_INET6
和AF_UNSPEC
)。如果解析主机名失败,会以error event调用回调函数。如果成功了,则会像 bufferevent_connect
一样,接着进行建立连接。
dns_base
参数是可选的。如果该参数为空,则Libevent会一直阻塞,等待主机名解析完成,一般情况下不会这么做。如果提供了该参数,则Libevent使用它进行异步的主机名解析。参考Chapter 9更多内容。
类似于bufferevent_socket_connect,该函数会告知Libevent,bufferevent上已存在的socket尚未建立连接,在解析完成,并且连接建立成功之前,不应该在其上进行读写操作。
如果发生了错误,有可能是DNS解析错误。可以通过调用函数bufferevent_socket_get_dns_error
函数得到最近发生的错误信息。如果该函数返回的错误码为0,则表明没有检查到任何DNS错误。
Example: Trivial HTTP v0 client.
/* 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;
}
6. 通用的bufferevent操作
本节介绍的bufferevent能够在不同的bufferevent实现上工作
6.1 释放bufferevent
void bufferevent_free(struct bufferevent *bev);
该函数释放bufferevent
。bufferevent
在内部具有引用计数,所以即使当释放bufferevent
时,如果bufferevent
还有未决的延迟回调,那该bufferevent在该回调完成之前也不会删除。
bufferevent_free
函数会尽快释放bufferevent
。然而,如果bufferevent
的输出缓冲区中尚有残留数据要写,该函数也不会在释放bufferevent
之前对缓冲区进行flush
。
如果设置了BEV_OPT_CLOSE_ON_FREE
标志,并且该bufferevent
有socket
或者使用了其他底层bufferevent作为其传输系统,则在释放该bufferevent
时,会关闭对应的传输系统。
这些函数在Libevent 0.8.中引入。
6.2 回调函数、水位线、使能操作
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()
可以改变bufferevent的回调函数。readcb
、writecb
和eventcb
函数 分别在 有足够数据可读、有足够数据可写、有event时间发生时被调用。 这些回调函数的第一个参数就是发生了events的bufferevent; 而最后一个参数则是由用户提供的bufferevent_setcb
的cbarg
参数;用户可以通过event回调函数的events
参数是event标志的位掩码:参考上面的“回调函数和水位线”一节
我们可以通过向bufferevent_setcb()
传递NULL
参数来禁用一些回调函数。需要注意的是,cbarg
参数是针对所有回调函数的。
我们可以通过向bufferevent_getcb
传递指针来获取当前的回调函数。该函数会将*readcb_ptr
设置为当前的读回调函数,*writecb_ptr
设置为写回调函数,*eventcb_ptr
设置为当前的event回调函数,并且*cbarg_ptr
设置为当前回调函数的参数。对于被设置为NULL
的指针,则会忽略它。
函数bufferevent_setcb()
在Libevent 1.4.4中引入. bufferevent_data_cb
和 bufferevent_event_cb
则是在 Libevent 2.0.2-alpha中新引入的. The 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);
我们可以在bufferevent上enable或者disable事件 EV_READ
, EV_WRITE
, or EV_READ|EV_WRITE
。 当disable了读写操作,则bufferevent不会读取或写入数据。
当output buffer 为空时,没必要disable写动作,bufferevent会自动禁止掉写动作。
类似的,当输入缓冲区达到它的高水位线的时候,没必要禁止读操作:bufferevent会自动停止读操作,而且在有空间读取的时候,又重新开启读操作。
默认情况下,新创建的bufferevent会enable写操作,而禁止读操作。
可以通过bufferevent_get_enabled()
来获取当前bufferevent中有那些events被enable了。
bufferevent_get_enabled()
是在 2.0.3-alpha中引入的, 而其它函数在Libevent 0.8就被引进了。
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark);
bufferevent_setwatermark
调整一个bufferevent的读水位线,或写水位线,或两者一起调整。如果在events参数中设置了EV_READ
参数,则会调整读水位线,如果设置了EV_WRITE
标志,则会调整写水位线。将高水位线标志置为0,表示“无限制”。
该函数在Libevent 1.4.4.中引入 。
Example
#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;
}
7. bufferevent中的数据操作
如果不能操作读写的数据,则从网络中读写数据没有任何意义。bufferevent提供函数可以操作读写的数据。
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
这两个函数功能非常强大,它们分别反悔bufferevent的input buffer
和 output buffer
. 在evbuffer类型上所能进行的所有操作,可以参考下一章。
注意,应用程序只能从input buffer中移走(而不是添加)数据,而且只能向output buffer添加(而不是移走)数据。
如果bufferevent上的写操作因为数据太少而停滞(或者读操作因为数据太多而停滞),则向output buffer中添加数据(或者从input buffer中移走数据)可以自动重启写(读)操作。
int bufferevent_write(struct bufferevent *bufev, const void*data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer*buf);
这些函数向bufferevent的output buffer中添加数据。调用bufferevent_write
函数添加data中的size个字节的数据到输出缓冲区的末尾。调用 bufferevent_write_buffer
函数则将buf中所有数据都移动到output buffer的末尾。这些函数返回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的input buffer中移走数据。bufferevent_read
函数从input buffer中移动size个字节到data
中。它返回实际移动的字节数。bufferevent_read_buffer
函数则移动输入缓冲区中的所有数据到buf
中,该函数返回0表示成功,返回-1表示失败。
注意bufferevent_read
函数中,data缓冲区必须有足够的空间保存size个字节。
bufferevent_read()
在 libevent 0.8中引入,bufferevent_read_buffer
在Libevent 2.0.1-alpha. 中才引入。
* Example*
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <ctype.h>
void
read_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 <= 0)
break; /* No more data. */
for (i=0; i<n; ++i)
tmp[i] = toupper(tmp[i]);
bufferevent_write(bev, tmp, n);
}
}
struct proxy_info {
struct bufferevent *other_bev;
};
void
read_callback_proxy(struct bufferevent *bev, void *ctx)
{
/* You might use a function like this if you're implementing
a simple proxy: it will take data from one connection (on
bev), and write it to another, copying as little as
possible. */
struct proxy_info *inf = ctx;
bufferevent_read_buffer(bev,
bufferevent_get_output(inf->other_bev));
}
struct count {
unsigned long last_fib[2];
};
void
write_callback_fibonacci(struct bufferevent *bev, void *ctx)
{
/* Here's a callback that adds some Fibonacci numbers to the
output buffer of bev. It stops once we have added 1k of
data; once this data is drained, we'll add more. */
struct count *c = ctx;
struct evbuffer *tmp = evbuffer_new();
while (evbuffer_get_length(tmp) < 1024) {
unsigned long next = c->last_fib[0] + c->last_fib[1];
c->last_fib[0] = c->last_fib[1];
c->last_fib[1] = next;
evbuffer_add_printf(tmp, "%lu", next);
}
/* Now we add the whole contents of tmp to bev. */
bufferevent_write_buffer(bev, tmp);
/* We don't need tmp any longer. */
evbuffer_free(tmp);
}
8. 读写 超时
同其他events一样,可以设置timeout时间,当bufferevent在timeout时间消逝后还没有成功的读或写任何数据,则可以触发某个超时事件。
void bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *timeout_read, const struct timeval *timeout_write);
将timeout
设置为NULL
,意味着移除超时时间;然而在Libevent 2.1.2-alpha版本之前,这种方式并非在所有event类型上都有效。(对于较老版本的,取消超时时间的有效方法是,可以将超时时间设置为好几天,并且/或者使eventcb
函数忽略BEV_TIMEOUT
事件)。
当bufferevent试图读取数据时,等待了timeout_read
秒还没有数据,则读超时事件就会触发。当bufferevent试图写数据时,至少等待了timeout_write
秒,则写超时事件就会触发。
注意,只有在bufferevent读或写的时候,才会对超时时间进行计时。换句话说,如果bufferevent上禁止了读操作,或者当输入缓冲区满(达到高水位线)时,则读超时时间不会使能。类似的,如果写操作未被使能,或者没有数据可写,则写超时时间也会被禁止。
当读或写超时发生的时候,则bufferevent上相应的读写操作就会被禁止。相应的event回调函数就会以BEV_EVENT_TIMEOUT|BEV_EVENT_READING
或BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING
进行调用。
9. 在bufferevent上进行flush
int bufferevent_flush(struct bufferevent *bufev,
short iotype, enum bufferevent_flush_mode state);
对一个bufferevent进行flush,可以强制bufferevent尽可能多的从底层传输系统上读取或者写入数据,而忽略其他可能阻止写入的限制条件。该函数的细节依赖于不同类型的bufferevent。
参数iotype
可以是EV_READ
, EV_WRITE
, EV_READ|EV_WRITE
指明处理读操作、写操作,还是两者都处理. 参数state
可以是BEV_NORMAL
, BEV_FLUSH
,BEV_FINISHED
之一. BEV_FINISHED
指明应该告诉另一端已经没有数据可以发送了;BEV_NORMAL
和 BEV_FLUSH
取决于bufferevent的类型。
bufferevent_flush
函数返回-1表示失败,返回0表示没有任何数据被flush,返回1表示由数据被flush。
目前(Libevent2.0.5-beta),bufferevent_flush
函数只在某些bufferevent类型上进行了实现,特别是基于socket
的bufferevent并不支持该操作。
10. 特定类型的bufferevent函数
这些函数并非在所有bufferevent类型上都能使用
int bufferevent_priority_set(struct bufferevent *bufev, int pri);
int bufferevent_get_priority(struct bufferevent *bufev);
该函数将实现bufev的events的优先级调整为pri,关于优先级更多的信息,可以参考event_priority_set函数。
返回0表示成功,返回-1表示失败,该函数只能工作在基于socket的bufferevent上。
函数bufferevent_priority_set()
在 Libevent 1.0 中引入; bufferevent_get_priority()
直到 2.1.2-alpha.才出现。
int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
该函数设置或者返回一个基于fd
的event
的文件描述符。只有基于socket
的bufferevent
支持setfd
操作。这些函数返回-1表示失败,setfd
返回0表示成功。
函数bufferevent_setfd()
在 Libevent 1.4.4
中引入。 bufferevent_getfd()
在 Libevent 2.0.2-alpha 中引入。
struct event_base *bufferevent_get_base(struct bufferevent *bev);
该函数返回bufferevent
的event_base
. 该函数在2.0.9-rc中引入 .
struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
如果bufferevent
作为其他bufferevent的底层传输系统的话,则该函数返回该底层bufferevent。参考过滤型bufferevent,获得关于这种情况的更多信息。
这个函数在Libevent 2.0.2-alpha.中引进。
11. 对bufferevent手动加锁或者解锁
类似于evbuffers
,有时希望保证在bufferevent上的一系列操作是原子性的。Libevent提供了可以手动加锁和解锁bufferevent的函数。
void bufferevent_lock(struct bufferevent *bufev);
void bufferevent_unlock(struct bufferevent *bufev);
注意,如果一个bufferevent在创建时没有指定BEV_OPT_THREADSAFE
标志,或者Libevent的线程支持功能没有激活,则加锁一个bufferevent没有作用。
通过该函数对bufferevent
进行加锁的同时,也会加锁evbuffers
。这些函数都是递归的:对一个已经加锁的bufferevent再次加锁是安全的。当然,对于每次锁定都必须进行一次解锁。
12. 过时的bufferevent函数
在Libevent1.4和Libevent2.0之间,bufferevent的后台代码经历了大量的修改。在老接口中,访问·bufferevent·结构的内部是很正常的,而且,还会经常使用依赖于这种访问方式的宏。
让问题变得更加复杂的是,老的代码中,有时会使用以evbuffer
为前缀的bufferevent
函数。
下表是一个Libevent2.0之前版本的函数概述
Current name | Old name |
---|---|
bufferevent_data_cb | evbuffercb |
bufferevent_event_cb | everrorcb |
BEV_EVENT_READING | EVBUFFER_READ |
BEV_EVENT_WRITE | EVBUFFER_WRITE |
BEV_EVENT_EOF | EVBUFFER_EOF |
BEV_EVENT_ERROR | EVBUFFER_ERROR |
BEV_EVENT_TIMEOUT | EVBUFFER_TIMEOUT |
bufferevent_get_input(b) | EVBUFFER_INPUT(b) |
bufferevent_get_output(b) | EVBUFFER_OUTPUT(b) |
老版本的函数定义在event.h中,而不是event2/bufferevent.h
中。
如果仍然需要访问bufferevent内部结构中的普通部分,可以包含event2/bufferevent_struct.h
。我们不建议这样做:bufferevent结构的内容在不同的Libevent版本中经常会发生改变。如果包含了event2/bufferevent_compat.h
文件,可以使用本节介绍的宏和名字。
老版本的代码中,创建bufferevent结构的接口有所不同:
struct bufferevent *bufferevent_new(evutil_socket_t fd,
evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg);
int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);
bufferevent_new()
函数仅能创建基于socket
的bufferevent
,而且还是在不推荐使用的“当前”event_base上。可以通过调用bufferevent_base_set
来调整一个socket bufferevent
的event_base
。
老版本的代码设置超时的秒数,而不是设置结构体timeval
:
void bufferevent_settimeout(struct bufferevent *bufev,
int timeout_read, int timeout_write);
最后注意,在Libevent2.0之前的版本中,底层的evbuffer
实现是非常低效的,因此使用bufferevent构建高性能的程序是不太可能的。