Libevent:7Bufferevents基本概念
很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存。比如,当写数据的时候,一般会经历下列步骤:
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就会停止读取,直到输入缓冲区中足够多的数据被抽走,从而数据量再次低于该水位线。默认情况下该水位线是无限制的,所以从来不会因为输入缓冲区的大小而停止读取操作。
,所以输出缓冲区被清空时才调用写回调函数。
写高水位线:并非由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
Libevent:7Bufferevents基本概念的更多相关文章
- memcached 源码阅读笔记
阅读 memcached 最好有 libevent 基础, memcached 是基于 libevent 构建起来的. 通由 libevent 提供的事件驱动机制触发 memcached 中的 IO ...
- 概念理解-Libevent
可移植性: 使用 LibEvent 编写的程序应该在 LibEvent 支持跨越的所有平台上工作,即使没有更好的方法来处理. 非阻塞式IO:LibEvent也应该支持一般的方法使程序可以运行在某些限制 ...
- libevent源码深度剖析
原文地址: http://blog.csdn.net/sparkliang/article/details/4957667 第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多 ...
- (转)Libevent(1)— 简介、编译、配置
转自:http://name5566.com/4190.html 参考文献列表:http://www.wangafu.net/~nickm/libevent-book/ 此文编写的时候,使用到的 Li ...
- 基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET
基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET 基于libevent, libuv和android L ...
- Libevent浅析
前段时间对Libevent的源码进行了阅读,现整理如下: 介绍 libevent是一个轻量级的开源高性能事件驱动网络库,是一个典型的Reactor模型.其主要特点有事件驱动,高性能,跨平台,统一事件源 ...
- 网络I/O中的同步、异步、阻塞和非阻塞概念
在学习网络编程过程中,经常会把这几个概念搞混淆. 同步I/O与异步I/O区别 我们先来看一下操作I/O时涉及的对象和步骤(这里我们以read为例): 这里会涉及到两个系统对象,一个是调用这个I/O的应 ...
- 轻量级网络库libevent初探
本文是关于libevent库第一篇博文,主要由例子来说明如何利用该库.后续博文再深入研究该库原理. libevent库简介 就如libevent官网上所写的“libevent - an event n ...
- Libevent教程001: 简介与配置
本文内容大致翻译自 libevent-book, 但不是照本翻译. 成文时, libevent最新的稳定版为 2.1.8 stable. 即本文如无特殊说明, 所有描述均以 2.1.8 stable ...
随机推荐
- 通过游戏学python 3.6 第一季 第五章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆 可复制直接使用 娱乐 可封装 函数
#猜数字--核心代码--猜测次数--随机函数和屏蔽错误代码---优化代码及注释--账号密码登陆 #!usr/bin/env python #-*-coding:utf-8-*- #QQ12411129 ...
- bzoj 4241 历史研究——分块(区间加权众数)
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4241 套路:可以大力预处理,如果求区间加权众数,可以预处理i~j块(或 j 位置)的最大值, ...
- java httpclient中文乱码解决方案,看注释
@RequestMapping("getpage") public ModelAndView admin_checkurl(HttpServletRequest request) ...
- oracle习题集-高级查询2
1.列出员工表中每个部门的员工数和部门编号 Select deptno,count(*) from emp group by deptno; 2.列出员工表中,员工人数大于3的部门编号和员工人数 ; ...
- SPOJ GSS5
GSS5 - Can you answer these queries V #tree You are given a sequence A[1], A[2], ..., A[N] . ( |A[i] ...
- NOIP模拟 6.30
Problem 1 护花(flower.cpp/c/pas) [题目描述] 约翰留下他的N(N<=100000)只奶牛上山采木.他离开的时候,她们像往常一样悠闲地在草场里吃草.可是,当他回来的时 ...
- 【Python之路22】冒泡排序算法
1.变量互换 a = 123 b = 456 temp = a a = b b = temp python比较简单的变量互换: a = 123 b = 456 a,b = b,a print(a,b) ...
- MySQL与PostgreSQL比较 哪个数据库更好
最后结论说的好,通常由团队成员的熟悉度来决定: PostgreSQL 的名字很少听到,最近试装发现不是很友好:官方文档写的对新手来说有点坑: 有数据库工作经验的直接看最后一句就可以. 如果打算为项目选 ...
- Python操作SQLite数据库的方法详解
Python操作SQLite数据库的方法详解 本文实例讲述了Python操作SQLite数据库的方法.分享给大家供大家参考,具体如下: SQLite简单介绍 SQLite数据库是一款非常小巧的嵌入式开 ...
- Dubbo+JStorm
Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于阿里巴巴集团的各成员站点.阿里巴巴近几年对开源社区的贡献不论在国内还是国外都是引人注目的,比如:JStorm捐赠给Apache并加入Apach ...