很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:
(1)决定要向连接写入一些数据,把数据放入到缓冲区中
(2)等待连接可以写入
(3)写入尽量多的数据
(4)记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入
这种缓冲IO模式很通用,libevent为此提供了一种通用机制,即bufferevent。bufferevent由一个底层的传输端口(如套接字),一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。
3.1 bufferevent和evbuffer
每个bufferevent都有一个输入缓冲区和一个输出缓冲区,它们的类型都是“struct evbuffer”。有数据要写入到bufferevent时,添加数据到输出缓冲区;bufferevent中有数据供读取的时候,从输入缓冲区抽取(drain)数据。
3.2 回调水位
每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。
每个bufferevent有四个水位:
(1)读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。
(2)读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。
(3)写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。
(4)写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。
bufferevent也有“错误”或者“事件”回调,用于向应用通知非面向数据的事件,如连接已经关闭或者发生错误。定义了下列事件标志:
BEV_EVENT_READING:#读取操作时发生某事件,具体是哪种事件请看其他标志。 BEV_EVENT_WRITING:#写入操作时发生某事件,具体是哪种事件请看其他标志。 BEV_EVENT_ERROR:#操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR(),获取错误号。 BEV_EVENT_TIMEOUT:#发生超时。 BEV_EVENT_EOF:#遇到文件结束指示。 BEV_EVENT_CONNECTED:#请求的连接过程已经完成。
3.3 延迟回调
默认情况下,bufferevent的回调在相应的条件发生时立即被执行。(evbuffer的回调也是这样的)在依赖关系复杂的情况下,这种立即调用会制造麻烦。比如说,假如某个回调在evbuffer A空的时候向其中移入数据,而另一个回调在evbuffer A满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。
要解决此问题,可以请求bufferevent(或者evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是在event_loop()调用中被排队,然后在通常的事件回调之后执行。
3.4 bufferevent的选项标志
BEV_OPT_CLOSE_ON_FREE:#释放bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。 BEV_OPT_THREADSAFE:#自动为bufferevent分配锁,这样就可以安全地在多个线程中使用bufferevent。 BEV_OPT_DEFER_CALLBACKS:#设置这个标志时,bufferevent延迟所有回调,如上所述。 #默认情况下,如果设置bufferevent为线程安全的,则bufferevent会在调用用户提供的回调时进行锁定。 #设置这个选项会让libevent在执行回调的时候不进行锁定。 BEV_OPT_UNLOCK_CALLBACKS:
3.5 与基于套接字的bufferevent一起工作
基于套接字的bufferevent是最简单的,它使用libevent的底层事件机制来检测底层网络套接字是否已经就绪,可以进行读写操作,并且使用底层网络调用(如readv、writev、WSASend、WSARecv)来发送和接收数据。
3.5.1 创建基于套接字的bufferevent
可以使用bufferevent_socket_new()创建基于套接字的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是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。
成功时函数返回一个bufferevent,失败则返回NULL。
3.5.2 在基于套接字的bufferevent上启动连接
如果bufferevent的套接字还没有连接上,可以启动新的连接。
int bufferevent_socket_connect(struct bufferevent *bev , struct sockaddr *address, int addrlen);
address和addrlen参数跟标准调用connect()的参数相同。如果还没有为bufferevent设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。
如果已经为bufferevent设置套接字,调用bufferevent_socket_connect()将告知libevent套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
连接完成之前可以向输出缓冲区添加数据。
如果连接成功启动,函数返回0;如果发生错误则返回-1。
注意:如果使用bufferevent_socket_connect()发起连接,将只会收到BEV_EVENT_CONNECTED事件。如果自己调用connect(),则连接上将被报告为写入事件。
3.6 通用bufferevent操作
3.6.1 释放bufferevent
void bufferevent_free(struct bufferevent *bev);
这个函数释放bufferevent。bufferevent内部具有引用计数,所以,如果释放bufferevent时还有未决的延迟回调,则在回调完成之前bufferevent不会被删除。
如果设置了BEV_OPT_CLOSE_ON_FREE标志,并且bufferevent有一个套接字或者底层bufferevent作为其传输端口,则释放bufferevent将关闭这个传输端口。
3.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函数将分别在已经读取足够的数据、已经写入足够的数据,或者发生错误时被调用。每个回调函数的第一个参数都是发生了事件的bufferevent,最后一个参数都是调用bufferevent_setcb()时用户提供的cbarg参数:可以通过它向回调传递数据。事件回调的events参数是一个表示事件标志的位掩码:请看前面的“回调和水位”节。
要禁用回调,传递NULL而不是回调函数。
注意:bufferevent的所有回调函数共享单个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将自动停止写入,然后在有数据等待写入时重新开始。
类似地,没有必要在输入缓冲区高于高水位时禁用读取事件: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,调整读取水位。如果events设置了EV_WRITE标志,调整写入水位)对于高水位,0表示“无限”。
3.6.3 操作bufferevent中的数据
写数据
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
这个函数向bufferevent的输出缓冲区添加数据。bufferevent_write()将内存中从data处开始的size字节数据添加到输出缓冲区的末尾。成功时这些函数都返回0,发生错误时则返回-1。
注意:即使没有调用bufferevent_enable使能写事件,调用bufferevent_write时,内部会添加写事件的监控,触发写回调函数后,再把写事件清除掉,写回调函数返回时,这个时候检查一下输出缓冲区是否低于写低水位,低了就调用一下写bufferevent的回调函数。
读数据
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
这个函数从bufferevent的输入缓冲区移除数据。bufferevent_read()至多从输入缓冲区移除size字节的数据,将其存储到内存中data处。
注意:对于bufferevent_read(),data处的内存块必须有足够的空间容纳size字节数据。
3.6.4 读写超时
跟其他事件一样,可以要求在一定量的时间已经流逝,而没有成功写入或者读取数据的时候调用一个超时回调。
void bufferevent_set_timeouts(struct bufferevent *bufev, const struct timeval *timeout_read, const struct timeval *timeout_write);
设置超时为NULL会移除超时回调。
试图读取数据的时候,如果至少等待了timeout_read秒,则读取超时事件将被触发。试图写入数据的时候,如果至少等待了timeout_write秒,则写入超时事件将被触发。
注意,只有在读取或者写入的时候才会计算超时。也就是说,如果bufferevent的读取被禁止,或者输入缓冲区满(达到其高水位),则读取超时被禁止。类似的,如果写入被禁止,或者没有数据待写入,则写入超时被禁止。
读取或者写入超时发生时,相应的读取或者写入操作被禁止,然后超时事件回调被调用,带有标志BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。
3.7 类型特定的bufferevent函数
这些bufferevent函数不能支持所有bufferevent类型。
int bufferevent_priority_set(struct bufferevent *bufev, int pri); int bufferevent_get_priority(struct bufferevent *bufev);
这个函数调整bufev的优先级为pri。关于优先级的更多信息请看event_priority_set()。
成功时函数返回0,失败时返回-1。这个函数仅能用于基于套接字的bufferevent。
int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
这些函数设置或者返回基于fd的事件的文件描述符。只有基于套接字的bufferevent支持setfd()。两个函数都在失败时返回-1;setfd()成功时返回0。
struct event_base *bufferevent_get_base(struct bufferevent *bev);
这个函数返回bufferevent的event_base。
3.8 手动锁定和解锁
有时候需要确保对bufferevent的一些操作是原子地执行的。为此,libevent提供了手动锁定和解锁bufferevent的函数。
void bufferevent_lock(struct bufferevent *bufev); void bufferevent_unlock(struct bufferevent *bufev);
注意:如果创建bufferevent时没有指定BEV_OPT_THREADSAFE标志,或者没有激活libevent的线程支持,则锁定操作是没有效果的。
用这个函数锁定bufferevent将自动同时锁定相关联的evbuffer。这些函数是递归的:锁定已经持有锁的bufferevent是安全的。当然,对于每次锁定都必须进行一次解锁。
3.9 示例代码
服务器tcp_server.c:
/************************************************************************* # File Name: tcp_server.c # Author: wenong # mail: [email protected] # Created Time: 2016年09月03日 星期六 21时51分08秒 ************************************************************************/ void read_buf_cb(struct bufferevent* bev, void* cbarg) { int ret, i; char buf[MAXBYTES]; ret = bufferevent_read(bev, buf, sizeof(buf)); printf("read_buf_cd length %d\n", ret); for(i = 0; i < ret; i++) { buf[i] = toupper(buf[i]); } bufferevent_write(bev, buf, ret); } void event_cb(struct bufferevent* bev, short event, void* cbarg) { struct event_base* base = (struct event_base*)cbarg; if(BEV_EVENT_READING & event) puts("BEV_EVENT_READING"); if(BEV_EVENT_WRITING & event) puts("BEV_EVENT_WRITING"); if(BEV_EVENT_ERROR & event) puts("BEV_EVENT_ERROR"); if(BEV_EVENT_EOF & event) { puts("BEV_EVENT_EOF"); bufferevent_free(bev); } } void accept_cb(evutil_socket_t serverfd, short what, void * arg) { struct sockaddr_in clientaddr; struct event* ev; struct bufferevent* bev; struct event_base* base = (struct event_base*)arg; int clientaddrlen; int clientfd; puts("Accept client connect\n"); clientaddrlen = sizeof(clientaddr); bzero((void*)&clientaddr, sizeof(clientaddr)); clientfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen); printf("recv clientfd %d\n", clientfd); evutil_make_socket_nonblocking(clientfd); bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb , NULL, (bufferevent_event_cb)event_cb, (void*)base); bufferevent_enable(bev, EV_READ); bufferevent_setwatermark(bev, EV_READ, 10, 0); } void main_loop(evutil_socket_t fd) { struct event_base * base; struct event* ev; base = event_base_new(); ev = event_new(base, fd, EV_READ | EV_PERSIST, (event_callback_fn)accept_cb, (void*)base); event_add(ev, NULL); puts("server begin listenning\n"); event_base_dispatch(base); event_free(ev); event_base_free(base); } int main(int argc, char** argv) { int serverfd; socklen_t serveraddrlen; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERVERPORT); serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serverfd = socket(AF_INET, SOCK_STREAM, 0); serveraddrlen = sizeof(serveraddr); bind(serverfd, (struct sockaddr*)&serveraddr, serveraddrlen); listen(serverfd, 128); main_loop(serverfd); close(serverfd); return 0; }
客户端tcp_client.c
/************************************************************************* # File Name: tcp_client.c # Author: wenong # mail: [email protected] # Created Time: 2016年09月03日 星期六 22时10分11秒 ************************************************************************/ void* cmd_msg_cb(evutil_socket_t stdinfd, short what, void* arg) { int ret; struct bufferevent* bev = (struct bufferevent*)arg; char buf[MAXBYTES]; puts("get msg from stdin:"); ret = read(stdinfd, buf, sizeof(buf)); bufferevent_write(bev, buf, ret); } void read_buf_cb(struct bufferevent* bev, void* cbarg) { int ret; char buf[MAXBYTES]; ret = bufferevent_read(bev, buf, sizeof(buf)); write(STDOUT_FILENO, buf, ret); } void event_cb(struct bufferevent* bev, short event, void* cbarg) { struct event_base* base = (struct event_base*)cbarg; if(BEV_EVENT_READING & event) puts("BEV_EVENT_READING"); if(BEV_EVENT_WRITING & event) puts("BEV_EVENT_WRITING"); if(BEV_EVENT_ERROR & event) puts("BEV_EVENT_ERROR"); if(BEV_EVENT_EOF & event) { puts("BEV_EVENT_EOF"); event_base_loopexit(base, NULL); //event_base_loopexit(bufferevent_get_base(bev), NULL); } } void main_loop(int clientfd) { struct event_base* base; struct bufferevent* bev; struct event*ev_stdin; base = event_base_new(); bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb , NULL, (bufferevent_event_cb)event_cb, (void*)base); bufferevent_enable(bev, EV_READ); ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST , (event_callback_fn)cmd_msg_cb, (void*)bev); event_add(ev_stdin, NULL); event_base_dispatch(base); bufferevent_free(bev); event_free(ev_stdin); event_base_free(base); puts("exit now..."); } int main(int argc, char** argv) { int clientfd; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(SERVERPORT); clientfd = socket(AF_INET, SOCK_STREAM, 0); connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); main_loop(clientfd); return 0; }