很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存。比如,当写数据的时候,一般会经历下列步骤:
l 决定向一个链接中写入一些数据;将数据放入缓冲区中;
l 等待该链接变得可写;
l 写入尽可能多的数据;
l 记住写入的数据量,如果还有数据需要写入,则需要再次等待链接变得可写。
这种IO缓冲模式很常见,因此Libevent为此提供了一种通用机制。“bufferevent”由一个底层传输系统(比如socket),一个读缓冲区和一个写缓冲区组成。普通的events,是在底层传输系统准备好读或写的时候就调用回调函数。而bufferevent则不同,它在已经写入或者读出数据之后才调用回调函数。
Libevent有多种bufferevent,它们共享通用的接口。截至本文撰写时,有下列bufferevent类型:
基于socket的bufferevents:在底层流式socket上发送和接收数据,使用event_*接口作为其后端。
异步IO的bufferevents:使用WindowsIOCP接口在底层流式socket上发送和接收数据的bufferevent。(仅限于Windows,实验性的)
过滤型bufferevent:在数据传送到底层bufferevent对象之前,对到来和外出的数据进行前期处理的bufferevent,比如对数据进行压缩或者转换
成对的bufferevent:两个相互传送数据的bufferevent
注意:截止Libevent2.0.2-alpha版本,bufferevent接口还没有完全覆盖所有的bufferevent类型。换句话说,并不是下面介绍的每一个接口都能用于所有的bufferevent类型。Libevent开发者会在未来的版本中解决该问题。
还要注意:bufferevent目前仅能工作在流式协议上,比如TCP。未来可能会支持数据报协议,比如UDP。
本文所有的函数和类型都是在<event2/bufferevent.h>文件中声明。与evbuffers相关的函数在<event2/buffer.h>中声明,有关信息参考下一章。
一:bufferevent和evbuffers
每一个bufferevent都有一个输入缓冲区和一个输出缓冲区。它们的类型都是“structevbuffer”。如果bufferevent上有数据输出,则需要将数据写入到输出缓冲区中,如果bufferevent上有数据需要读取,则需要从输入缓冲区中进行抽取。
evbuffer接口支持很多操作,会在以后的章节中进行讨论。
二:回调函数和“水位线”
每一个bufferevent都有两个数据相关的回调函数:读回调函数和写回调函数。默认情况下,当从底层传输系统读取到任何数据的时候会调用读回调函数;当写缓冲区中足够多的数据已经写入到底层传输系统时,会调用写回调函数。通过调整bufferevent的读取和写入“水位线”(watermarks),可以改变这些函数的默认行为。
每个bufferevent都有4个水位线:
读低水位线:当bufferevent的输入缓冲区的数据量到达该水位线或者更高时,bufferevent的读回调函数就会被调用。该水位线默认为0,所以每一次读取操作都会导致读回调函数被调用。
读高水位线:如果bufferevent的输入缓冲区的数据量到达该水位线时,那么bufferevent就会停止读取,直到输入缓冲区中足够多的数据被抽走,从而数据量再次低于该水位线。默认情况下该水位线是无限制的,所以从来不会因为输入缓冲区的大小而停止读取操作。
写低水位线:当写操作使得输出缓冲区的数据量达到或者低于该水位线时,才调用写回调函数。默认情况下,该值为0,所以输出缓冲区被清空时才调用写回调函数。
写高水位线:并非由bufferevent直接使用,对于bufferevent作为其他bufferevent底层传输系统的时候,该水位线才有特殊意义。所以可以参考后面的过滤型bufferevent。
bufferevent同样具有“错误”或者“事件”回调函数,用来通知应用程序关于非数据引起的事件,比如关闭连接或者发生错误。定义了下面的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上请求链接过程已经完成
三:延迟回调函数
默认情况下,当相应的条件发生的时候,bufferevent回调函数会立即执行。(evbuffer的回调也是这样的,随后会介绍)当依赖关系变得复杂的时候,这种立即调用就会有问题。比如一个回调函数用来在evbuffers A变空时将数据移入到该缓冲区,而另一个回调函数在evbuffer A变满时从其中取出数据进行处理。如果所有这些调用都发生在栈上的话,在依赖关系足够复杂的时候,有栈溢出的风险。
为了解决该问题,可以通知bufferevent(以及evbuffer)应该延迟回调函数。当条件发生时,延迟回调函数不是立即调用,而是在event_loop()调用中被排队,然后在常规的event回调之后执行。
四: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的锁。
五:基于socket的bufferevent
最简单的bufferevents就是基于socket类型的bufferevent。基于socket的bufferevent使用Libevent底层event机制探测底层网络socket何时准备好读和写,而且使用底层网络调用(比如readv,writev,WSASend或WSARecv)进行传送和接受数据。
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。
提示:要确保提供给bufferevent_socket_new的socket是非阻塞模式。Libevent提供了便于使用的evutil_make_socket_nonblocking来设置非阻塞模式。
bufferevent_socket_new成功时返回一个bufferevent,失败时返回NULL。
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.
#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 to127.0.0.1:8080. Ordinarily we'd do
something here, like start readingor 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;
}//(非阻塞情况下,应该就是返回-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)函数。
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);
该函数解析主机名,查找family类型的地址(family的类型可以是AF_INET, AF_INET6和AF_UNSPEC)。如果解析主机名失败,会以错误event调用回调函数。如果成功了,则会像 bufferevent_connect一样,接着进行建链。
dns_base参数是可选的。如果该参数为空,则Libevent会一直阻塞,等待主机名解析完成,一般情况下不会这么做。如果提供了该参数,则Libevent使用它进行异步的主机名解析。参考第九章了解DNS更多内容。
类似于bufferevent_socket_connect,该函数会告知Libevent,bufferevent上已存在的socket尚未建链,在解析完成,并且建链成功之前,不应该在其上进行读写操作。
如果发生了错误,有可能是DNS解析错误。可以通过调用函数bufferevent_socket_get_dns_error函数得到最近发生的错误信息。如果该函数返回的错误码为0,则表明没有检查到任何DNS错误。
/*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("DNSerror: %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.xclient\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操作
本节介绍的函数可以工作在多种bufferevent实现上。
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时,会关闭该传输系统。
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的一个或多个回调函数。当读取了数据,写入数据或者event发生的时候,就会相应的调用readcb、writecb和eventcb函数。这些函数的第一个参数就是发生event的bufferevent,最后一个参数是bufferevent_setcb的cbarg参数:可以使用该参数传递数据到回调函数。event回调函数的events参数是event标志的位掩码:参考上面的“回调函数和水位线”一节。
可以通过传递NULL来禁止一个回调。注意bufferevent上的所有回调函数共享一个cbarg,所以改变改值会影响到所有回调函数。
可以通过向bufferevent_getcb函数传递指针来检索bufferevent当前设置的回调函数,该函数会将*readcb_ptr设置为当前的读回调函数,*writecb_ptr设置为写回调函数,*eventcb_ptr设置为当前的event回调函数,并且*cbarg_ptr设置为当前回调函数的参数。如果任何一个指针设置为NULL,则会被忽略。
void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);
short bufferevent_get_enabled(struct bufferevent *bufev);
可以将bufferevent上的EV_READ,EV_WRITE或EV_READ|EV_WRITE使能或者禁止。如果禁止了读取和写入操作,则bufferevent不会读取和写入数据。
当输出缓冲区为空时,禁止写操作是不必要的:bufferevent会自动停止写操作,而且在有数据可写时又会重启写操作。
类似的,当输入缓冲区达到它的高水位线的时候,没必要禁止读操作:bufferevent会自动停止读操作,而且在有空间读取的时候,又重新开启读操作。
默认情况下,新创建的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参数,则会调整读水位线,如果设置了EV_WRITE标志,则会调整写水位线。将高水位线标志置为0,表示“无限制”。
#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 thebufferevent and make sure it gets
connected... */
/* Trigger the read callback only wheneverthere 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;
}
七:在bufferevent中操作数据
如果不能操作读写的数据,则从网络中读写数据没有任何意义。bufferevent提供函数可以操作读写的数据。
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
这两个函数可以返回读写缓冲区中的数据。在evbuffer类型上所能进行的所有操作,可以参考下一章。
注意,应用程序只能从输入缓冲区中移走(而不是添加)数据,而且只能向输出缓冲区添加(而不是移走)数据。
如果bufferevent上的写操作因为数据太少而停滞(或者读操作因为数据太多而停滞),则向输出缓冲区中添加数据(或者从输入缓冲区中移走数据)可以自动重启写(读)操作。
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个字节。
#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 frombev's input buffer 128
bytes at a time, uppercases it, andstarts sending it
back.
(Watch out! In practice, you shouldn't use toupper toimplement
a network protocol, unless you knowfor a fact that the current
locale is the one you want to beusing.)
*/
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);
}
}
structproxy_info {
struct bufferevent *other_bev;
};
void read_callback_proxy(struct bufferevent *bev, void* ctx)
{
/* You might use a function like thisif you're implementing
a simple proxy: it will take datafrom 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));
}
structcount {
unsigned long last_fib[2];
};
void write_callback_fibonacci(struct bufferevent *bev, void * ctx)
{
/* Here's a callback that adds someFibonacci 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 tmpto bev. */
bufferevent_write_buffer(bev, tmp);
/* We don't need tmp any longer. */
evbuffer_free(tmp);
}
八:读写超时
同其他events一样,某段时间过去之后,bufferevent还没有成功的读或写任何数据,则可以触发某个超时事件。
void bufferevent_set_timeouts(struct bufferevent *bufev,
conststruct 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进行调用。
九:在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并不支持该操作。
十:特定类型的bufferevent函数
下列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上。
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表示成功。
struct event_base * bufferevent_get_base(struct bufferevent *bev);
该函数返回一个bufferevent的event_base。
struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
如果bufferevent作为其他bufferevent的底层传输系统的话,则该函数返回该底层bufferevent。参考过滤型bufferevent,获得关于这种情况的更多信息。
十一:在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再次加锁是安全的。当然,对于每次锁定都必须进行一次解锁。
十二:过时的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构建高性能的程序是不太可能的。
原文:http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html