BEV_EVENT_ERROR获取详细错误信息
BEV_EVENT_ERROR
表示bufferevent在操作时有错误发生。需要更多关于错误信息,调用EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT
if(events & BEV_EVENT_EOF)
{
printf("connection closed \n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("err = %s\n", strerror(EVUTIL_SOCKET_ERROR()));
}
else if(events & BEV_EVENT_CONNECTED)
{
printf("已经连接了服务器.....\n");
}
示例:
err = Connection refused
bufferevent基础和概念 | popozhu update:2021-10-2 很多时候,
一个程序除了响应事件之外还想缓存一些数据。比如当我们想写数据,通常会用这样的模式:向一个连接写一些数据,先把数据存放到一个buffer里 等待连接变成可写状态 尽可能写入数据
记住我们写了多少数据,以及还有多少数据未写,然后等待下次连接再变成可写状态。
Libevent为这种带缓存的IO模式提供了一个通用的机制。一个”bufferevent”包含一个底层传输(比如socket),一个读buffer和一个写buffer。当底层传输可读写时就调用回调函数,这是普通事件的处理,而bufferevent是在读或写足够的数据时才调用用户指定的回调函数。有多种类型的bufferevent使用相同的接口,比如下面的类型:
socket-based bufferevents 这种bufferevent的底层传输为stream
socket,使用event_*这种接口作为它的后端。 asynchronous-IO bufferevents
限于Windows,实验中。这种类型的bufferevent使用Windows IOCP接口来读写数据。 filtering
bufferevents
在数据被传送到bufferevent事件对象之前,这种buffevent会对将要读或写的数据进行预处理,比如压缩或转换数据。 paired
bufferevents 相互之间传输数据的一对bufferevent。 注意: 在Libevent
2.0.2-alpha这个版本,bufferevent的接口还不能通用于上面列出的bufferevent类型,也就是说,下面列出的每个接口不一定适用于所有bufferevent类型。未来版本中可能会改善。再次注意: bufferevent当前只能适用于面向流的协议,比如TCP,而面向数据报文的协议,比如UDP,未来可能也会支持。
bufferevent和缓存 每个bufferevent都有一个输入缓存和一个输出缓存,对应的数据结构为struct
evbuffer。当你对一个bufferevent写数据,数据写入到输出缓存;当有数据可读时,你是从输入缓存中提取数据。evbuffer的接口支持多种操作,后一节中会讨论到。
回调函数和水位标志(watermark)
每个bufferevent有2个相关连的回调函数:读回调和写回调。默认地,当从底层传输读到数据,读回调就被调用,当输出缓存的数据被清空,写回调就调用。但你可以调整bufferevent的“水位标志”来覆盖这些函数的默认行为。每个bufferevent有4个水位标志:
读低水位Read low-water mark
当一个读操作让bufferevent的输入缓存达到或超出这个水位,读回调就被调用。默认为0,所以每个读操作都会导致读回调被执行。
读高水位Read high-water mark
当bufferevent的输入缓存达到这个水位,bufferevent停止读取数据,直到数据被取出,再次低于这个水位为止。
默认是不做限制,所以会不停地读取数据到输入缓存里。 写低水位Write low-water mark
当一个输出缓存达到或低于这个水位,写回调就被调用。默认为0,所以只有当输出缓存的数据全部发送出去之后,写回调才被调用。 写高水位Write
high-water mark
bufferevent不直接使用这个水位,当一个bufferevent被用于另一个bufferevent的地层传输时,这个标志可以有特殊的含义。见下面的filtering
bufferevents。
一个bufferevent也有一个error和event回调函数,这2个函数与数据无关,当一个连接被关闭或出现错误,它们会被调用来通知到应用程序。定义了如下的事件标志:BEV_EVENT_READING 表示当bufferevent在读操作时有一个事件到来。 BEV_EVENT_WRITING
表示当bufferevent在写操作时有一个事件到来。 BEV_EVENT_ERROR
表示bufferevent在操作时有错误发生。需要更多关于错误信息,调用EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT bufferevent的超时事件。 BEV_EVENT_EOF 到达文件末尾。
BEV_EVENT_CONNECTED bufferevent完成一个另一端发起请求的连接。延迟的回调
默认上,当对应的情况发生,buffevent回调函数会马上被执行。(对于evbuffer的回调函数也是如此)当存在复杂依赖时这个立刻调用却反而带来麻烦。比如有一个回调函数,当evbuffer
A里的数据为空时执行,把数据写到buffer里,另有一个回调函数,当evbuffer
A的buffer数据满了时实行,把数据从buffer里读出。由于这些回调都在栈上执行,如果依赖足够冗长,则可能会出现栈溢出。为了解决这个问题,你可以告诉bufferevent(或者evbuffer)应该推迟执行它的回调函数。这样当条件满足后回调不会立刻执行,而是进入event_loop()的队列里,当队列里的常规回调函数执行完毕后,这个延迟的回调函数才被调用。
bufferevent可选的标志 在创建一个bufferevent时你可以使用一个或多个标志。可用的标志有:
BEV_OPT_CLOSE_ON_FREE
当bufferevent被释放时,关闭底层传输。这会关闭一个底层的socker,释放一个底层的bufferevent等。
BEV_OPT_THREADSAFE 自动为bufferevent分配锁,所以可安全用于多线程。
BEV_OPT_DEFER_CALLBACKS 如果设置了这个标志,bufferevent推迟所有回调的执行,如上所述。
BEV_OPT_UNLOCK_CALLBACKS
默认上若一个bufferevent设置成线程安全,则当用户提供的回调调用时会获取该bufferevent的锁。设置这个标志来让Libevent在完成你的回调函数后释放bufferevent的锁。使用基于socket的bufferevent
基于socket的bufferevent使用起来最简单。基于socket的bufferevent使用Libevent的底层事件机制来检测底层网络socket是否可读写,也使用底层网络调用(比如readv,writev,WSASend,或者WSARecv)来传输和接收数据。创建一个基于socket的bufferevent 使用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则是可选的socket fd,如果你想稍后再设置fd,可以传-1。
提示:
确保你提供的socket是非阻塞模式的,Libevent提供了一个便捷的函数evutil_make_socket_nonblocking()来设置一个socket为非阻塞。如果成功,这个函数返回一个bufferevent,如果失败,返回NULL。
在bufferevent上启用连接 如果bufferevent的socket还没连接,你可以创建一个连接,接口:
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen); 参数address和addrlen跟标准的connect()一样。如果bufferevent还没有一个socker,调用这个函数会为它创建一个新的socket,并设置socket为非阻塞。
如果bufferevent已经有一个socket,调用bufferevent_socket_connect()告诉Libevent该socket未连接,在连接成功之前不会有读或写操作返回。
在连接返回前向输出buffer添加数据则是可以的。
如果连接成功建立,这个函数返回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_socket_connect()来触发调用connect(),你只会有一个BEV_EVENT_CONNECTED事件。如果你自己调用connect(),该连接触发的是一个写事件。
使用hostname来建立连接 更普遍的做法是把解析host和创建连接合并为一个操作,这里有一个接口:
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,查找地址的类属(AF_INET,AF_INET6和AF_UNSPEC)。如果解析失败则调用错误回调函数,如果成功则创建一个连接。
参数dns_base可选,如果为NULL,Libevent会阻塞直到完成域名查找,而这一般非你所愿。如果不为NULL,Libevent会使用它来异步查找域名。
跟bufferevent_socket_connect()一样,这个函数告诉Libevent任何已有的socket是未连接的,在完成域名解析且连接成功创建和返回前,该连接的读或写操作不应返回。
如果出现错误,可能是DNS域名查找出错,你可以调用bufferevent_socket_get_dns_error()来查看最近的错误。如果让会的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; }
bufferevent通用操作 这节的函数适用于多种bufferevent实现。
释放一个bufferevent void bufferevent_free(struct bufferevent *bev);
Bufferevent在内部有引用计数,如果当你释放它时它有延迟的回调函数,则再回调被执行前它不会被释放。然而bufferevent_free()会尝试尽快释放bufferevent,如果bufferevent上有等待写的数据,在bufferevent被释放前它可能不会刷新缓存。
如果设置了BEV_OPT_CLOSE_ON_FREE标志,且该buffereavent有一个socket或一个底层bufferevent,这个底层传输会在释放时被关闭。
操作回调、设置水位和启用功能 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在可读、可写或有事件被触发时被调用(各自被调用)。最后一个用户提供的参数cbarg将作为参数传给bufferevent_callcb():你可以使用它来传递数据给你的回调函数。回调函数的参数events则是事件标志的位掩码。
你若给某个回调函数入参传一个NULL指针,则禁用该回调函数。注意到所有的回调函数共享入参cbarg,修改该参数会影响所有回调函数。
启用/禁用 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会自动停止写,当有缓存里有数据时会自动重新开始写socket。
类似的,当输入缓存达到最高水位时也没必要禁用读操作,bufferevent会自动停止再读入数据,当输入缓存又有空间时bufferevent会自动重新开始读入数据到缓存。
默认上,一个新创建的bufferevent启用了写操作,但不包括读操作(读写buffer的操作)。
可以调用bufferevent_get_enabled()来查看bufferevent当前启用了哪些事件。设置水位 void bufferevent_setwatermark(struct bufferevent *bufev, short
events,size_t lowmark, size_t highmark); 函数bufferevent_setwatermark()调整读、写水位,或都调整。
如果参数events设置了EV_READ,则调整读水位,如果设置EV_WRITE则调整写水位。
高水位如果设置为0,则表示不限制。
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; } 操作bufferevent里的数据 bufferevent提供一些函数让你可以向网络中读写数据:
Interface struct evbuffer *bufferevent_get_input(struct bufferevent
bufev); struct evbuffer bufferevent_get_output(struct bufferevent *bufev); 这两个基础函数非常有用,分别返回输入缓存和输出缓存。evbuffer更多操作函数,见下节。注意,程序可能只从输入缓存里读取数据(而不添加数据),只向输出缓存里添加数据(而不删除)。
如果输出缓存里太少数据而导致bufferevent的写操作被推迟,可以向输出缓存里添加数据,bufferevent就会自动重新开始把输出缓存里的数据写socket。对于输入缓存也类似。
Interface int bufferevent_write(struct bufferevent *bufev,
const void *data, size_t size); int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf); 这2个函数添加数据到输出缓存里,调用bufferevent_write()把内存里data位置的size字节添加到输出缓存的末尾。调用bufferevent_write_buffer()则把buf的整块内存添加到输出缓存的末端,并把buf的数据删除。
成功返回0,遇到出错返回-1。
Interface size_t bufferevent_read(struct bufferevent *bufev, void
data, size_t size); int bufferevent_read_buffer(struct bufferevent bufev,struct evbuffer *buf); 这2个函数从输入缓存里读取数据。调用bufferevent_read()从输入缓存里删除size字节,存到内存的data位置,返回被删除的字节数。bufferevent_read_buffer()则删除输入缓存里的所有数据,存放到buf里,成功返回0,失败返回-1。
注意使用bufferevent_read(),内存data位置一定要有足够的空间来容纳size字节的数据。
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); } 读和写的超时 跟其他事件一样,bufferevent如果一定时间之后都没有读写数据,会执行超时回调函数。
Interface void bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *timeout_read, const struct timeval *timeout_write); 传NULL给超时入参来删除对应的超时回调,然后在Libevent 2.1.2-alpha之前并不适用所有的事件类型。 注意,超时时间只有在bufferevent尝试读或写才开始计算,换句话说,如果bufferevent禁用读操作,或者输入缓存满了(达到最高水位),读操作超时时间不会启用。同样,写操作超时时间也并不会被启用如果禁用了些操作,或者没有数据可写。
如果读或写超时发生了,对应的读或写操作会被停止。事件回调函数会被执行,以标识BEV_EVENT_TIMEOUT|BEV_EVENT_READING或BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING。
刷新bufferevent Interface int bufferevent_flush(struct bufferevent
*bufev,short iotype, enum bufferevent_flush_mode state); 刷新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,如果刷新了数据,返回1。
类型相关的bufferevent函数 这些函数并不支持所有类型的bufferevent。
Interface int bufferevent_priority_set(struct bufferevent *bufev, int
pri); int bufferevent_get_priority(struct bufferevent *bufev);
调整bufev的优先级为pri。参见event_priority_set()获取更多优先级的信息。成功返回0,失败返回-1,只适用于基于socket的bufferevent。
Interface int bufferevent_setfd(struct bufferevent *bufev,
evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct
bufferevent *bufev);
设置或获取基于fd事件的文件描述符fd,只支持基于socket的bufferevent。失败返回-1,setfd()成功则返回0。Interface struct event_base *bufferevent_get_base(struct bufferevent
*bev); 返回bufferevent的event_base。Interface struct bufferevent *bufferevent_get_underlying(struct
bufferevent *bufev);
当底层传输使用的是另一个bufferevent,通过这个函数来获取这个底层传输的bufferevent。手工加锁或解锁bufferevent
跟evbuffer一样,有时候你想确保bufferevent上得一些操作是原子操作的。Libevent提供供你手工加锁和解锁的函数。Interface void bufferevent_lock(struct bufferevent *bufev); void
bufferevent_unlock(struct bufferevent *bufev);
注意,如果bufferevent在创建时如果没有提供BEV_OPT_THREADSAFE线程,或者启用Libevent的线程支持,加锁一个bufferevent是没有效果的。加锁bufferevent也会锁上它关联的evbuffer。这些函数是可以重入的:对一个已获取的锁再次加锁是安全的,当然,也要由对应的解锁调用。
Referenced from:https://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%25E5%259F%25BA%25E7%25A1%2580%25E5%2592%258C%25E6%25A6%2582%25E5%25BF%25B5/