libevent源码分析--evbuffer和bufferevent的关系

时间:2020-12-15 00:17:06

在libevent中还有一个很重要的部分。evbuffer和bufferevent。

evbuffer是一个缓冲区,用户可以向evbuffer添加数据,evbuffer和bufferevent经常一起使用,或者说bufferevent使用了evbuffer,bufferevent有两个evbuffer缓冲,还有event。

说明bufferevent是一个带有缓冲区的I/O。可以使用它们!用户可以通过bufferevent相关的函数往evbuffer缓冲区中添加需要发送的内同,内部机制可以保证发送。当有数据在bufferevent上的套接字上时,数据被读入到bufferevent内部的evbuffer缓冲区!

     这样一来,用户程序需要交互的就是使用bufferevent相关函数想bufferevent内部的两个evbuffer(一个为读缓冲,一个为写缓冲)写数据/取数据,发送和接受都是bufferevent内部实现的。当然内部的发送和读取都是非阻塞的!!

    其中evbuffer并没有显示的使用malloc在堆区申请空间,而是使用了内核为每个套接字在栈上分配的事件,在这个空间不够时,再申请空间存放数据,使用函数evbuffer_expand()函数。具体的步骤是怎么回事呢?(这里分析的是libevent-1.4.12-stable)

   一、首先申请bufferevent

         使用bufferevent_new函数申请一个struct bufferevent.首先是malloc了一些结构体,然后调用了两个重量级的函数,event_set,这个函数被调用两次,分别对应bufferevent中的ev_read和ev_write。这个函数是干什么的,在不久以前的某个时刻也注重分析了,就是让某一个套接字初始化给具体的event,并且初始化这个event所要关注的事件和事件处理函数,这么一来,bufferevent中的两个event被初始化了!暂且不说注册的两个函数(bufferevent_readcb bufferevent_writecb),这也是重量级的。

       然后调用函数bufferevent_setcb函数,这个函数注册层的回调函数,这函数处理了从evbuffer中读取数据后的相应处理(怎么处理就是用户层的事情了)

二、调用bufferevent_enable函数

       这个函数中同样调用了一个以前的重量级函数event_add(),这个函数可以理解为将某个event添加到event_base中,这样这个套接字就可以被处理,等到有数据到达就会被触发。其实到目前为止,已经可以结束了,上下的就是自己写bufferevent_setcb函数中注册的两个函数了。

       整天看着两步,再对比以前使用libevent的流程,之前多一个event_init全局初始化,然后多一个event_dispatch()函数,也就是说,这个bufferevent知识多了两个evbuffer,然后对event_set函数和event_add函数进行再次封装!

三、bufferevent_readcb()

       这个函数是在buffer_new函数中别event_set函数调用的,event_set也就是对传递给的event参数做相应的初始化,但是bufferevent_readcb函数的具体内容是什么呢?

      咱们这里不考虑什么读/写的标准,都是默认值。在这个函数中调用了evbuffer_read函数,这个函数可以直接调用了系统调用read(),,读取的函数就是直接调用bufferevent_add函数继续关注这个event

四、bufferevent_write()函数

     其实和上面的那个函数类似,调用了evbuffer_write函数,这个函数总调用了write函数,然后调用bufferevent_add函数继续关注这个事件

这是使用bufferevent的整个过程!!

   其实想要知道libevent是怎么使用evbuffer,还需要知道是怎么调用的!



2 回调和水位

每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。

每个bufferevent有四个水位:

读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。

读取高水位:输入缓冲区的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。

写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。

写入高水位:bufferevent没有直接使用这个水位。它在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:请求连接过程已经完成。

上述标志由2.0.2-alpha新引入

3 延迟回调

默认情况下,bufferevent的回调在相应的条件发生时立即被执行。(evbuffer的回调也是这样的,随后会介绍)在依赖关系复杂的情况下,这种立即调用会制造麻烦。比如说,假如某个回调在evbuffer A空的时候向其中移入数据,而另一个回调在evbuffer A满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。

要解决此问题,可以请求bufferevent(或者evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是event_loop()调用中被排队,然后在通常的事件回调之后执行。

(延迟回调由libevent 2.0.1-alpha引入)