很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:
- 决定要向连接写入一些数据,把数据放入到缓冲区中;
- 等待连接可以写入;
- 写入尽量多的数据;
- 记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入;
这种缓冲IO模式很通用,libevent为此提供了一种通用机制,即bufferevent。bufferevent由一个底层的传输端口(如套接字)、一个读取缓冲区、一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。有多种共享公用接口的bufferevent类型,编写本文时已存在以下类型:
- 基于套接字的bufferevent:使用event_*接口作为后端,通过底层流式套接字发送或者接收数据的bufferevent;
- 异步IObufferevent:使用Windows IOCP接口,通过底层流式套接字发送或者接收数据的bufferevent(仅用于Windows,试验中);
- 过滤型bufferevent:将数据传输到底层bufferevent对象之前,处理输入或者输出数据的bufferevent;比如说,为了压缩或者转换数据;
- 成对的bufferevent:相互传输数据的两个bufferevent;
注意:截止2.0.2-alpha版,这里列出的bufferevent接口还没有完全正交于所有的bufferevent类型。也就是说,下面将要介绍的接口不是都能用于所有bufferevent类型,libevent开发者在未来版本中将修正这个问题。
也请注意:当前bufferevent只能用于像TCP这样的面向流的协议,将来才可能会支持像UDP这样的面向数据报的协议。
本节描述的所有函数和类型都在event2/bufferevent.h中声明。特别提及的关于evbuffer的函数声明在event2/buffer.h中,详细信息请参考下一章。
1 bufferevent和evbuffer
每个bufferevent都有一个输入缓冲区和一个输出缓冲区,它们的类型都是"struct evbuffer"。有数据要写入到bufferevent时,添加数据到输出缓冲区;bufferevent中有数据供读取的时候,从输入缓冲区抽取(drain)数据。evbuffer接口支持很多种操作,后面的章节将讨论这些操作。
2 回调和水位
每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整bufferevent的读取和写入"水位(watermarks)"可以覆盖这些函数的默认行为。
每个bufferevent有四个水位:
- 读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。即:只要从底层IO缓冲区读取数据后,bufferevent输入缓冲区中会有数据,导致数据量大于低水位0,导致读取回调被调用;这里说的读取是针对底层IO缓冲区
- 读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。高水位是防止bufferevent输入缓冲区数据过多导致溢出,因此数据量到达高水位后,会停止从底层IO中读取数据,实际不存在这种情况;这里说的读取是针对底层IO缓冲区
- 写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。写入操作是将bufferevent输出缓冲区中的数据写入到底层IO,写入后,bufferevent输出缓冲区数据量会减少,如果剩余数据量等于低水位,表示数据都写完了,写入回调被调用;这里说的写入是针对底层IO缓冲区
- 写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。请看后面关于过滤型bufferevent的介绍;这里说的写入是针对底层IO缓冲区
bufferevent也有"错误"或者"事件"回调,用于向应用通知非面向数据的事件,如连接已经关闭或者发生错误。定义了下列事件标志:
- BEV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
- BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志;
- BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR();
- BEV_EVENT_TIMEOUT:发生超时;
- BEV_EVENT_EOF:遇到文件结束指示;
- BEV_EVENT_CONNECTED:请求的连接过程已经完成;
3 延时回调
默认情况下,buffe