zoukankan      html  css  js  c++  java
  • libevent源码学习(17):缓冲管理框架

    目录

    Libevent缓冲区类型

    Libevent缓冲区结构

    缓冲区的读出与写入

    缓冲区的读入与写出

    缓冲区水位机制

    缓冲区回调机制

    延迟回调机制
    Libevent缓冲区类型

           Libevent中提供了多种类型的缓冲区:基于套接字的缓冲区、针对Windows IOCP的bufferevent、在传输和接收数据之前进行数据处理(比如压缩)的过滤型缓冲区和成对传输的缓冲区。本文及后面的内容都仅针对基于套接字的缓冲区展开分析。
    Libevent缓冲区结构

           Libevent实际上是由链表来实现一个缓冲区的,链表中的每一个结点都用来存放数据,整个缓冲区的数据也就存放在这一个个的链表结点之中,链表结点由evbuffer_chain数据类型定义。

           因此,整个链表就可以看做是一个缓冲区的数据集合,而管理整个链表则是由evbuffer数据类型实现,在该数据类型中可以指出缓冲区的头尾结点以及最后一个带数据的结点。

           通过上述两种数据结构,也就可以实现对缓冲区的增加删除数据等功能。

           对于每一个文件描述符,Libevent都为其设定了读缓冲区和写缓冲区,也就是说每一个fd都对应两个缓冲区。Libevent使用bufferevent数据类型来管理一个fd所对应的读写缓冲区及其相关信息。如下所示。

    缓冲区的读出与写入

            缓冲区的读出与写入是指用户与读写缓冲区之间的交互:当用户需要从fd中读取数据时,实际上是从读缓冲区中读出数据;当用户需要向fd中写入数据时,实际上是向写缓冲区中写入数据。Libevent为用户的读和写都设置了接口(bufferevent_read和bufferevent_write),当用户调用这些接口时,并不是真的就把数据写到fd的内核缓冲区中或者从fd的内核缓冲区中读出。那么,知道了用户和缓冲区之间的交互,那缓冲区和真正的fd内核缓冲区之间又是如何交互的呢?
    缓冲区的读入与写出

            缓冲区的读入与写出是指缓冲区与fd的内核缓冲区进行交互:读缓冲区从fd的内核缓冲区中读入数据,写缓冲区将数据写出到fd的内核缓冲区。读入和写出数据的时机,是靠libevent的基本事件处理框架来判断的。依然是监听fd的可读和可写事件,当fd可读时,就会触发相应读监听事件(bufferevent中的ev_read),回调函数就会把数据从fd的内核缓冲区中读到读缓冲区中;当fd可写时,就会触发相应的写监听事件(bufferevent中的ev_write),回调函数就会把数据从写缓冲区中写到fd的内核缓冲区中。
    缓冲区水位机制

            对于每一个缓冲区,Libevent都设置了相应的高低水位。所谓“水位”,实际上就是对每个缓冲区设置的高低阈值,用来衡量缓冲区中的数据量。Libevent并未使用写缓冲区高水位,因此实际上有以下三种水位:

        读高水位:当读缓冲区中的数据量达到高水位,说明此时读缓冲区链表太长,此时就不应该再从fd读取数据了;

        读低水位:如果从fd中读取数据之后,读缓冲区的数据量低于低水位,相当于“几乎没读到什么数据”,那么bufferevent就不会去关注这次读取操作;

        写低水位:如果向fd中写出数据之后,写缓冲区的数据量高于低水位,相当于“几乎没写出什么数据”,那么bufferevent就不会去关注这次写出操作。

    缓冲区回调机制

           Libevent总是在一些“应当进行一些处理”的时候,调用回调函数。

           前面设置了水位,那我们怎么知道缓冲区什么时候达到/超过/低于水位了呢?这个时候就通过回调函数来实现:当缓冲区的数据量达到了相应水位,那么就应该进行相应的回调来执行一些特殊的处理。举个例子,当读缓冲区数据量达到或超过读高水位,那么就应当停止从fd中读取数据,而这个“停止读取”的行为,就在回调函数中实现。

           除此之外,Libevent还为每个缓冲区维护了一个回调队列,提供了用户向回调队列中添加或删除回调函数的接口,这些回调函数会在每次缓冲区发生变化的时候,调用回调函数,并告诉回调函数“这次缓冲区变化增加/减少了多少数据”。
    延迟回调机制

           由于用户可以向缓冲区中的回调队列任意添加回调函数,所以无法知道用户添加的回调函数到底要做什么,而用户也不知道Libevent中内置的回调函数何时调用,这样一来就可能存在用户回调和内置回调之间的递归调用,从而可能造成栈溢出。举个例子:如果用户添加了一个回调函数A,A会在缓冲区空的时候向缓冲区中写入数据,另一个回调函数B,会在缓冲区满的时候从缓冲区中抽取数据,由于缓冲区一旦改变就会立刻调用回调队列中的所有函数,因此就有可能A在返回之前,缓冲区处理回调队列时又调用了函数B,如果依赖关系足够复杂,B可能又调用回A,这样就会在前面的函数A还没返回,其他函数就开始调用,从而造成栈溢出。

           因此Libevent采用延迟回调机制,如果现在需要立刻调用回调函数,Libevent就会用一个“代表”来“代表”这些回调函数,把“代表”放到主循环的激活队列中。当主循环处理这个“代表”时,这个时候再回来调用所有回调函数。这样的好处在于,当缓冲区改变时并不会立刻再次调用回调队列中的函数,而是会进行“延时调用”,当再次处理回调队列的时候,函数A已经返回了,这样也就防止了多次递归的情况,也就避免了栈溢出。

     

     

           后面的文章,再来分析一下Libevent是如何去实现这些的。
    ————————————————
    版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_28114615/article/details/100037869

  • 相关阅读:
    关于JQ的$.deferred()
    JS去掉数组的重复项
    JS中iframe相关的window.self,window.parent,window.top
    JQ的live(),on(),deletage(),bind()几个的区别
    个人对闭包的理解
    ajax 代码
    Don't use runAllManagedModulesForAllRequests="true" when getting your MVC routing to work
    SQL语句收集
    性能速度
    ADO.NET(SqlConnection、SqlCommand、SqlDataAdapter、SqlTransaction、SqlParameter、DataSet)
  • 原文地址:https://www.cnblogs.com/cnhk19/p/14545736.html
Copyright © 2011-2022 走看看