很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:
(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(),则连接上将被报告为写入事件。