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
libevent源码学习(17):缓冲管理框架的更多相关文章
- libevent源码学习
怎么快速学习开源库比如libevent? libevent分析 - sparkliang的专栏 - 博客频道 - CSDN.NET Libevent源码分析 - luotuo44的专栏 - 博客频道 ...
- libevent源码学习(11):超时管理之min_heap
目录min_heap的定义向min_heap中添加eventmin_heap中event的激活以下源码均基于libevent-2.0.21-stable. 在前文中,分析了小顶堆min_h ...
- libevent源码学习(10):min_heap数据结构解析
min_heap类型定义min_heap函数构造/析构函数及初始化判断event是否在堆顶判断两个event之间超时结构体的大小关系判断堆是否为空及堆大小返回堆顶event分配堆空间堆元素的上浮堆元素 ...
- libevent源码学习(8):event_signal_map解析
目录event_signal_map结构体向event_signal_map中添加event激活event_signal_map中的event删除event_signal_map中的event以下源码 ...
- libevent源码学习(9):事件event
目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...
- libevent源码学习(6):事件处理基础——event_base的创建
目录前言创建默认的event_baseevent_base的配置event_config结构体创建自定义event_base--event_base_new_with_config禁用(避免使用)某一 ...
- libevent源码学习(2):内存管理
目录 内存管理函数 函数声明 event-config.h 函数定义 event_mm_malloc_ event_mm_calloc_ event_mm_strdup_ event_mm_reall ...
- libevent源码学习(1):日志及错误处理
目录 错误处理函数 函数声明 __attribute__指令 函数定义 可变参数宏 _warn_helper函数 日志处理 event_log日志处理入口 日志处理回调函数指针log_fn 设置日志处 ...
- libevent源码学习(7):event_io_map
event_io_map 哈希表操作函数 hashcode与equals函数 哈希表初始化 哈希表元素查找 哈希表扩容 哈希表元素插入 哈希表元素替换 哈希表元素删除 自定义条件删除元素 哈希表第一个 ...
随机推荐
- AGC050B Three Coins
做的时候有思考到是否能转化成移动点问题,但是没有清晰的把他解释出来. NOIP的时候也一样,T3也有考虑到是否能转为差分,但是也没有清晰的写出来. 自己做题的时候应尽量保证草稿纸和思绪的清晰,而不是在 ...
- lilo.conf
描述 默认情况下,本文件 ( /etc/lilo.conf ) 由引导管理程序 lilo 读取 (参考 lilo(8)). 它看起来可能象这样: boot = /dev/hda delay = 40 ...
- 问题记录:SNP 标记 phasing
GATK4 检测的SNP标记,有些位点会在检测过程中完成 phasing,在后续做基因型填充的时候有坑. GATK4 phasing 结果的缺失位点不是 ./. 也不是 .|. 而是直接变成一个单独 ...
- cpu的性能测试
#!/bin/bash #user%加上sys%是性能的评判标准 User_sys_a=`sar -u 1 3 |tail -1 |awk '{print $3"+"$5}'|bc ...
- Linux—linux 查看一个文件有多少M
ls -l --block-size=M #就把目录下的所有文件按M单位呈现
- kubernetes部署 etcd 集群
本文档介绍部署一个三节点高可用 etcd 集群的步骤: etcd 集群各节点的名称和 IP 如下: kube-node0:192.168.111.10kube-node1:192.168.111.11 ...
- MySQL插入大量数据探讨
笔者想进行数据库查询优化探索,但是前提是需要一个很大的表,因此得先导入大量数据至一张表中. 准备工作 准备一张表,id为主键且自增: 方案一 首先我想到的方案就是通过for循环插入 xml文件: &l ...
- python APScheduler模块
简介 一般来说Celery是python可以执行定时任务, 但是不支持动态添加定时任务 (Django有插件可以动态添加), 而且对于不需要Celery的项目, 就会让项目变得过重. APSchedu ...
- 漏洞检测方法如何选?详解源代码与二进制SCA检测原理
摘要:本文探讨的是SCA具体的检测原理,源代码SCA检测和二进制SCA检测有哪些相同点和不同点,在进行安全审计.漏洞检测上各自又有什么样的优势和适用场景. 本文分享自华为云社区<源代码与二进制文 ...
- 学习java 7.15
学习内容: 进程:正在运行的程序 是系统进行资源分配和调用的独立单位 每个进程都有它自己的内存空间和系统资源 线程:是进程中的单个顺序控制流,是一条执行路径 单线程:一个进程如果只有一条执行路径,则称 ...