深究muduo网络库的Buffer类!!!

最近在学习了muduo库的Buffer类,因为这个编程思想,今后在各个需要缓冲区的项目编程中都可以用到,所以今天来总结一下!

Buffer的数据结构

muduo的Buffer的定义如下,其内部是 一个 std::vector,且还存在两个size_t类型的readerIndex_,writerIndex_标识来表示读写的位置。

std::vector<char> buffer_;
size_t readerIndex_;
size_t writerIndex_;

结构图如下:
在这里插入图片描述
readIndex、writeIndex把整个vector内容分为3块:prependable、readable、writable,各块大小关系:

  • prependable = readIndex
  • readable = writeIndex - readIndex
  • writable = buffer.size() - writeIndex

Buffer设计思路

  • 定义了预留的prependable初始大小,以及Buffer的初始大小,代码如下:
static const size_t kCheapPrepend = 8;   //缓冲区头部
static const size_t kInitialSize = 1024; //缓冲区读写初始大小
  • 构造函数对Buffer进行了初始化,初始时两个标志位都指向kCheapPrepend,代码如下:
explicit Buffer(size_t initialSize = kInitialSize): buffer_(initialSize + kCheapPrepend), readerIndex_(kCheapPrepend), writerIndex_(kCheapPrepend){}
  • 利用三个函数,通过标志位,求出了可读可写以及预留区的大小,代码如下:
size_t readableBytes() const { return writerIndex_ - readerIndex_; }
size_t writerableBytes() const { return buffer_.size() - writerIndex_; }
size_t prependableBytes() const { return readerIndex_; }
  • 利用peek()函数,求出缓冲区可读数据的起始位置,代码如下:
const char* peek() const
{return begin() + readerIndex_; 
}
  • 通过一系列函数,对标志位进行重置操作
void retrieve(size_t len) //len表示已经读了的
{if(len < readableBytes()) {//已经读的小于可读的,只读了一部分len//还剩readerIndex_ += len 到 writerIndex_readerIndex_ += len; }else //len == readableBytes(){retrieveAll();
}void retrieveAll() //都读完了
{readerIndex_ = writerIndex_ = kCheapPrepend;
}
  • 计算数组的起始地址
char* begin()
{return &*buffer_.begin(); //vector底层数组元素的地址,也就是数组的起始地址
}
const char* begin() const
{return &*buffer_.begin();
}
  • 把onMessage函数上报的Buffer数据,转成string类型的数据返回。
std::string retrieveAllAsString()
{return retrieveAsString(readableBytes());//应用可读取数据的长度
}
std::string  retrieveAsString(size_t len)
{std::string result(peek(),len); //从起始位置读len长retrieve(len);return result;
}
  • 计算剩余可写的缓冲区长度,若可写的小于要写入的要进行扩容。
void ensureWriterableBytes(size_t len)
{if (writerableBytes() < len){makeSpace(len); //扩容}     
}
  • 把[data ,data+len]内存上的数据,添加到writeable缓冲区当中,首先会判断以下能不能写入,如果不足,先扩容,代码如下:
void append(const char* data, size_t len) //添加数据
{ensureWriterableBytes(len);std::copy(data,data+len,beginWrite());writerIndex_ += len;
}
char* beginWrite() {return begin() + writerIndex_; }
const char* beginWrite() const {return begin() + writerIndex_; }

如何扩容呢?

void makeSpace(size_t len)
{if (prependableBytes() + writerableBytes() < len + kCheapPrepend){buffer_.resize(writerIndex_ + len);}else{size_t readable = readableBytes(); //保存一下没有读取的数据std::copy(begin()+readerIndex_, begin()+writerIndex_, begin()+ kCheapPrepend); //挪一挪readerIndex_ = kCheapPrepend;writerIndex_ = readerIndex_+readable;}
}

扩容巧妙思想在于,因为两个指针的不断移动,导致指向可读数据的指针一直后移,预留区越来越大,如果一味的扩容,会导致前面预留区越来越大,这样造成了浪费,所以muduo库采用了以下思路进行判断,何时需要扩容:

  • 利用prependableBytes() + writerableBytes() 判断了整个Buffer上面剩余的可写入的空间,如果这个空间小于要写入的以及预留的8字节位置,那么直接扩容!!
  • 如果大于说明目前剩余的位置还足够存放要写入的数据,那么通过vector的数据拷贝,把Buffer里面的数据挪一挪,这时候readerIndex_就指向了初始位置,writerIndex_的位置就是目前可写入的首地址,这样在进行写入,就不需要一味的扩容。

如何从从fd上读取数据?

ssize_t readFd(int fd,int* saveErrno);

整体思路如下:

ssize_t Buffer::readFd(int fd,int* saveErrno)
{char extrabuf[65536] = { 0 }; //栈上内存空间struct iovec vec[2];const size_t writable = writerableBytes(); //buffer底层缓冲区剩余的可写的空间大小vec[0].iov_base = begin() + writerIndex_;vec[0].iov_len = writable;vec[1].iov_base = extrabuf;vec[1].iov_len = sizeof extrabuf;const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;const ssize_t n = ::readv(fd, vec, iovcnt);if(n < 0){*saveErrno = errno;}else if(n <= writable) //buffer可写的缓冲区已经够存储读取出来的数据{writerIndex_ += n;}else //extrabufl里面也写入了数据{writerIndex_ = buffer_.size();append(extrabuf,n-writable);  //writerIndex_ 开始写n-writable的数据}return n;
}

巧妙点在哪里呢?

我们在读数据的时候,不知道数据的最终大小是多少,所以采用了如下的方法:

  • 首先定义了一个64K栈缓存extrabuf临时存储,利用栈的好处是可以自动的释放,并计算出目前剩余可写的空间大小;
  • 利用结构体 iovec 指定了两块缓冲区,一块是目前剩余的可写的Buffer,一个是临时的缓冲区,指定了起始位置以及缓冲区的大小;
  • const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; 如果writable < sizeof extrabuf就选2块内存,否则一块就够用;
  • 读数据const ssize_t n = ::readv(fd, vec, iovcnt);
  • 若读取的数据超过现有内部buffer_的writable空间大小时, 启用备用的extrabuf 64KB空间, 并将这些数据添加到内部buffer_的末尾。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/8480.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell编程之条件语句

条件测试 文件测试与整数测试 字符串测试与逻辑测试 if语句 if单分支语句 if双分支语句 if多分支语句 case分支语句 条件测试操作 条件测试操作 1 条件判断 test命令测试表达式是否成立&#xff0c;若成立返回0.否则返回其它数值。 格式 1 test 条件表达式 格式 2 …

【Redis7】了解Redis

1.常见数据库 1.1.键值存储数据库 如 Map 一样的key-value 对&#xff0c;典型代表就是 Redis。 1.2.列存储数据库 关系型数据库是典型的行存储数据库&#xff0c;按行存储的数据在物理层面占用的是连续存储空间&#xff0c;不适合海量数据存储。而按列存储则可实现分布式存储&…

猫不爱喝水是正常的?求求別再被洗脑了!日常可以补水的主食分享

猫不爱喝水正常吗&#xff1f;看给猫喂的什么&#xff0c;喂的罐头的话不爱喝水问题不大。喂的干粮猫还长期不喝水&#xff0c;处于缺水状态&#xff0c;可能会出现便秘、上火、尿黄、尿少等症状。在高温的夏季&#xff0c;猫还可能因脱水而中暑&#xff0c;严重时甚至可能导致…

用c++实现汉诺塔问题、归并排序

6.1.3 汉诺塔问题 【问题】 汉诺塔问题(Hanio tower problem)来自一个古老的传说&#xff1a;有一座宝塔&#xff08;塔A),其上有64个金碟&#xff0c;所有碟子按从大到小由塔底堆放至塔顶。紧挨着这座宝塔有另外两座宝塔&#xff08;塔B和塔C),要求把塔A上的碟子移动到塔C上…

01-xss基本原理

核心:攻击的是前端&#xff0c; 一、课程引入 1、开发一个简单的PHP页面&#xff0c;代码如下&#xff1a; <?php // xss 基础演示代码&#xff1a;从浏览器中接受一个URL地址参数名为content if(isset($_GET[content])){$content$_GET[content];echo "你输入的内容…

再议大模型微调之Zero策略

1. 引言 尽管关于使用Deepspeed的Zero策略的博客已经满天飞了&#xff0c;特别是有许多经典的结论都已经阐述了&#xff0c;今天仍然被问到说&#xff0c;如果我只有4块40G的A100&#xff0c;能否进行全量的7B的大模型微调呢&#xff1f; 正所谓“纸上得来终觉浅&#xff0c;…

进程状态与优先级

Linux内核源代码&#xff1a; 首先我们需要明确一点&#xff0c;Linux操作系统和操作系统的进程状态是不同的 上图大概标识了各个状态对应在操作系统的状态 普通进程 R运行状态&#xff08;running&#xff09;: 并不意味着进程一定在运行中&#xff0c;它表明进程要么是在…

ROS 2边学边练(44)-- 从头开始构建一个视觉机器人模型

前言 从此篇开始我们就开始接触URDF(Unified Robot Description Format&#xff0c;统一机器人描述格式)&#xff0c;并利用其语法格式搭建我们自己的机器人模型。 动动手 开始之前我们需要确认是否安装joint_state_publisher功能包&#xff0c;如果有安装过二进制版本的urdf_…

解密某游戏的数据加密

前言 最近有个兄弟通过我的视频号加我&#xff0c;咨询能否将这个dubo游戏游戏开始前就将数据拿到从而进行押注&#xff0c;于是通过抓包工具测试了下&#xff0c;发现数据有时候是明文&#xff0c;有时候确实密文&#xff0c;大致看了下有这几种加密&#xff1a;Md5aes、Md5&a…

网络层协议之 IP 协议

IP 协议格式 4 位版本&#xff1a;此处的取值只有两个&#xff0c;4&#xff08;IPv4&#xff09;和 6&#xff08;IPv6&#xff09;&#xff0c;即指定 IP 协议的版本。 4 位首部长度&#xff1a;描述了 IP 报头多长&#xff0c;IP 报头是变长的&#xff0c;因为报头中的选项部…

点餐小程序 点餐系统 微信点餐系统 支持微信小程序 支付公众号 可接入第三方配送 全开源uniapp

餐饮连锁v2版-体验后台&#xff08;复制粘贴以下地址到浏览器&#xff0c;打开网址即可登录&#xff0c;) 本文来自&#xff1a;点餐小程序 点餐系统 微信点餐系统 支持微信小程序 支付公众号 可接入第三方配送 全开源uniapp - 源码1688 演示后台&#xff1a;https://diancan.…

异构图神经网络——Heterogeneous Graph Neural Networks

相关代码见文末 1.回顾同构图 1.1 GNN GNN基本计算方法——邻接矩阵乘以节点,聚合相邻节点的特征,得到本节点的特征表达 1.2 Graph Attention Network 引入图注意力,实现边的权重可学习,最简单的方法是,将两个节点的特征进行拼接,使用一组可学习的权重参数映射为边的权…

华为数据之道第一部分导读

目录 导读 第一部分 序 第1章 数据驱动的企业数字化转型 非数字原生企业的数字化转型挑战 业态特征&#xff1a;产业链条长、多业态并存 运营环境&#xff1a;数据交互和共享风险高 IT建设过程&#xff1a;数据复杂、历史包袱重 数据质量&#xff1a;数据可信和一致化…

学习大数据,所需更要的shell基础(2)

文章目录 read读取控制台输入函数系统函数bashnamedirname 自定义函数Shell工具&#xff08;重点&#xff09;cutawk 正则表达式入门常规匹配常用特殊字符 read读取控制台输入 1&#xff09;基本语法 read (选项) (参数) ①选项&#xff1a; -p&#xff1a;指定读取值时的提示…

C++初识多态(1)

1.多态要解决的问题&#xff08;引入&#xff09; 任何一种机制的存在&#xff0c;必然是有其存在的意义的&#xff0c;例如我们前面学过的函数重载&#xff0c;运算符重载&#xff0c;以及引用等等&#xff0c;都是解决一些特殊问题的&#xff1b; 下面通过一些具体的例子&a…

组合模式(Composite)——结构型模式

组合模式(Composite)——结构型模式 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能通过通用接口像独立整体对象一样使用它们。如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 例如一个场景…

剁手党必看——转转红包使用规则与最优组合计算全解析

​ 1、省钱攻略基础之“了解平台红包使用规则” 2、举个栗子 3、最优红包组合计算方法进化过程 3.1、初代“笛卡尔乘积”版 3.2、二代“边算边比较Map聚合”版 3.3、三代“边算边比较数组索引定位”版 4、总结 1、省钱攻略基础之“了解平台红包使用规则” 规则一&#x…

介绍 ffmpeg.dll 文件以及ffmpeg.dll丢失怎么办的五种修复方法

ffmpeg.dll 是一个动态链接库文件&#xff0c;属于 FFmpeg运行库。它在计算机上扮演着非常重要的角色&#xff0c;因为它提供了许多应用程序和操作系统所需的功能和组件。当 ffmpeg.dll 文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行&#xff0c;甚至系统崩溃。下面…

紫外激光打标机适合在哪些材料表面进行标记

紫外激光打标机适合在多种材料表面进行标记&#xff0c;特别是那些对热敏感或者需要高精度、高清晰度标记的材料。以下是一些常见的适用材料&#xff1a; 1. 塑料&#xff1a;紫外激光打标机在塑料材料上表现尤为出色&#xff0c;因为紫外激光的短波长和高能量密度使得它能够在…

Python | Leetcode Python题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; class Solution:def climbStairs(self, n: int) -> int:a, b 1, 1for _ in range(n - 1):a, b b, a breturn b