很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。libevent为此提供了一种通用机制,即bufferevent。
struct bufferevent {
/** Event base for which this bufferevent was created. */
struct event_base *ev_base;
/** Pointer to a table of function pointers to set up how this
bufferevent behaves. */
const struct bufferevent_ops *be_ops;
/** A read event that triggers when a timeout has happened or a socket
is ready to read data. Only used by some subtypes of
bufferevent. */
struct event ev_read;
/** A write event that triggers when a timeout has happened or a socket
is ready to write data. Only used by some subtypes of
bufferevent. */
struct event ev_write;
/** An input buffer. Only the bufferevent is allowed to add data to
this buffer, though the user is allowed to drain it. */
struct evbuffer *input;
/** An input buffer. Only the bufferevent is allowed to drain data
from this buffer, though the user is allowed to add it. */
struct evbuffer *output;
struct event_watermark wm_read;
struct event_watermark wm_write;
bufferevent_data_cb readcb;
bufferevent_data_cb writecb;
/* This should be called 'eventcb', but renaming it would break
* backward compatibility */
bufferevent_event_cb errorcb;
void *cbarg;
struct timeval timeout_read;
struct timeval timeout_write;
/** Events that are currently enabled: currently EV_READ and EV_WRITE
are supported. */
short enabled;
};
bufferevent 由一个底层的传输端口,一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调。
每个 bufferevent 有两个数据相关的回调,一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调。输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整 bufferevent 的读取和写入 “水位 (watermarks )” 可以覆盖这些函数的默认行为。
上述源码可以看到bufferevent 结构体中有两个类型为event_watermark的数据成员,event_watermark又含有两个数据成员,所以应该容易知道共有四种水位:【读取/写入】【高/低】水位
struct event_watermark wm_read;
struct event_watermark wm_write;
struct event_watermark {
size_t low;
size_t high;
};
可以使用bufferevent_socket_new()
创建基于套接字的 bufferevent。
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int options);
fd是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。
options是bufferevent选项,可以使用一个或者多个标志修改其行为。可识别的标志有:
int bufferevent_socket_connect(struct bufferevent *bev, const struct sockaddr *sa,
int socklen);
该函数参数与connect函数类似。建议学习libevent之前一定要学会socket编程
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。
如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect()
将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。连接完成之前可以向输出缓冲区添加数据。
void bufferevent_free(struct bufferevent *bev);
这个函数释放 bufferevent。如果释放时还有未决的延迟回调,则在回调完成之前 bufferevent 不会被删除。
如果设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层 bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口。
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, 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函数将分别在已经读取足够的数据、已经写入足够的数据,或者发生错误时被调用 。
每个回调函数的第一个参数都是发生了事件的bufferevent ,最后一个参数都是调用bufferevent_setcb()
时用户提供的 cbarg 参数,可以通过它向回调传递数据。事件回调的 what 参数是一个表示事件标志的位掩码
要禁用回调,传递 NULL 而不是回调函数。
注意: bufferevent 的所有回调函数共享单个 cbarg , 所以修改它将影响所有回调函数。
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
int bufferevent_enable(struct bufferevent *bufev, short event);
int bufferevent_disable(struct bufferevent *bufev, short event);
short bufferevent_get_enabled(struct bufferevent *bufev);
默认情况下,新创建的 bufferevent 的写入是启用的,但是读取没有启用。可以调用 bufferevent_get_enabled()
确定 bufferevent 上当前启用的事件。
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark);
bufferevent_setwatermark()
函数调整单个 bufferevent 的读取水位、写入水位,或者同时调整二者。(如果 events 参数设置了 EV_READ,调整读取水位。如果 events 设置了 EV_WRITE 标志,调整写入水位)
ps:对于高水位,0表示“无限”。
示例:
struct info
{
const char *name; // 缓冲区名
size_t total_drained; // 缓冲区数据量
};
void read_callback(struct bufferevent *bev, void *ctx)
{
struct info *inf = (struct info *)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 = (struct info *)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 *bf = NULL;
struct info *info1;
info1 = (struct info *)malloc(sizeof(struct info));
info1->name = "buffer1";
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(bf, EV_READ, 128, 0); // 注意高水位传参0表示无限
bufferevent_setcb(bf, read_callback, NULL, event_callback, info1);
bufferevent_enable(bf, EV_READ); /* Start reading. */
return bf;
}
bufferevent 还提供了下列函数用于观察要写入或者读取的数据。
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
这两个函数提供了非常强大的基础,它们分别返回输入和输出缓冲区
如果写入操作因为数据量太少而停止(或者读取操作因为太多数据而停止 ),则向输出缓冲区添加数据(或者从输入缓冲区移除数据)将自动重启操作。
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()
将内存中从 data 处开始的 size 字节数据添加到输出缓冲区的末尾 。bufferevent_write_buffer()
移除 buf 的所有内容,将其放置到输出缓冲区的末尾。成功时这些函数都返回0,错误时返回-1。
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()
,data 处的内存块必须有足够的空间容纳 size 字节数据。
int bufferevent_flush(struct bufferevent *bufev, short iotype,
enum bufferevent_flush_mode mode);
清空 bufferevent 要求 bufferevent 强制从底层传输端口读取或者写入尽可能多的数据,而忽略其他可能保持数据不被写入的限制条件。函数的细节功能依赖于 bufferevent 的具体类型。
iotype 参数应该是 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE,用于指示应该处理读取、写入,还是二者都处理。 mode参数可以是 BEV_NORMAL、BEV_FLUSH 或者 BEV_FINISHED。BEV_FINISHED 指示应该告知另一端,没有更多数据需要发送了; 而 BEV_NORMAL 和 BEV_FLUSH 的区别依赖于具体的 bufferevent 类型。
失败时 bufferevent_flush()返回-1,如果没有数据被清空则返回 0,有数据被清空则返回 1。
// 实现
int bufferevent_flush(struct bufferevent *bufev, short iotype,
enum bufferevent_flush_mode mode)
{
int r = -1;
BEV_LOCK(bufev);
if (bufev->be_ops->flush)
r = bufev->be_ops->flush(bufev, iotype, mode);
BEV_UNLOCK(bufev);
return r;
}
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; // ipv4
base = event_base_new();
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET; // tcp/ip协议族 ipv4
// 网络字节序转换
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(8080); /* Port 8080 */
// 创建基于套接字的bufferevent,并设置释放bufferevent时关闭底层传输端口。
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;
}