[muduo网络库]——muduo库Buffer类(剖析muduo网络库核心部分、设计思想)

news2024/9/20 16:25:31

接着之前我们[muduo网络库]——muduo库Socket类(剖析muduo网络库核心部分、设计思想),我们接下来继续看muduo库中的Buffer类。其实Buffer在我的另一篇博客里面已经介绍过了深究muduo网络库的Buffer类!!!,这里还是和这个之前几个类的方法保持一致,在梳理一遍Buffer类,给大家提供一个舒适的观感~

Buffer类

Buffer类封装了一个用户缓冲区,以及向这个缓冲区写数据读数据等一系列控制方法。

为什么要用Buffer

non-blocking网络编程中,non-blocking IO核心思想是避免阻塞在read()/write()或其他IO系统调用上,可以最大限度复用thread-of-control,让一个线程能服务于多个socket连接。而IO线程只能阻塞在IO-multiplexing函数上,如select()/poll()/epoll_wait(),这样应用层的缓冲区就是必须的,每个TCP socket都要有stateful的input buffer和output buffer。

设计思想

先来谈谈Buffer类整体的设计思想。因为这个编程思想,今后在各个需要缓冲区的项目编程中都可以用到。
muduo的Buffer的定义如下,其内部是 一个 std::vector,且还存在两个size_t类型的readerIndex_writerIndex_标识来表示读写的位置。结构图如下:
在这里插入图片描述
readIndex、writeIndex把整个vector内容分为3块:prependable、readable、writable,各块大小关系:

  • prependable = readIndex
  • readable = writeIndex - readIndex
  • writable = buffer.size() - writeIndex
    Buffer类是可以动态扩容的,在下面的成员函数中,会详细介绍。

重要成员变量

static const size_t kCheapPrepend = 8;  //缓冲区头部
static const size_t kInitialSize = 1024; //缓冲区读写初始大小
std::vector<char> buffer_;
size_t readerIndex_;
size_t writerIndex_;

他们的作用就显而易见了

重要成员函数

  • 通过构造函数,使两个标志位都指向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_; 
}
  • 重置标志位readerIndex_writerIndex_
void retrieve(size_t len) //len表示已经读了的
{
    if(len < readableBytes()) 
    {
       //已经读的小于可读的,只读了一部分len
       //还剩readerIndex_ += len 到 writerIndex_
       readerIndex_ += len; 
    }
    else //len == readableBytes()
    {
       retrieveAll();
}

void retrieveAll() //都读完了
{
    readerIndex_ = writerIndex_ = kCheapPrepend;
}
  • 把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;
}
  • 将data数据添加到缓冲区中,并且更新writerIndex_ 指向。
void append(const char* data, size_t len) //添加数据
{
    ensureWriterableBytes(len);
    std::copy(data, data+len, beginWrite());
    writerIndex_ += len;
}

先来看看beginWrite,开始写得位置。

char* beginWrite() {return begin() + writerIndex_; }
const char* beginWrite() const {return begin() + writerIndex_; }

注意到这里有一个ensureWriterableBytes,它判断了缓冲区还有多少能写的位置,以及扩容

void ensureWriterableBytes(size_t len)
{
   if (writerableBytes() < len)
   {
        makeSpace(len); //扩容
   }     
}
  • 如何正确扩容,这就是Buffer的精髓了
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_的位置就是目前可写入的首地址,这样在进行写入,就不需要一味的扩容。
    过程图如下:
    在这里插入图片描述在这里插入图片描述
  • 客户端发来数据,readFd从该TCP接收缓冲区中将数据读出来并放到Buffer中。
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;
}

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

  1. 首先定义了一个64K栈缓存extrabuf临时存储,利用栈的好处是可以自动的释放,并计算出目前剩余可写的空间大小;
  2. 利用结构体 iovec 指定了两块缓冲区,一块是目前剩余的可写的Buffer,一个是临时的缓冲区,指定了起始位置以及缓冲区的大小;
  3. const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; 如果writable < sizeof extrabuf就选2块内存readv,否则一块就够用;
  4. 读数据const ssize_t n = ::readv(fd, vec, iovcnt);
  5. 若读取的数据超过现有内部buffer_的writable空间大小时, 启用备用的extrabuf 64KB空间, 并将这些数据添加到内部buffer_的末尾。
  • 服务端要向这条TCP连接发送数据,将Buffer中的数据拷贝到TCP发送缓冲区中。
ssize_t Buffer::writeFd(int fd,int* saveErrno)
{
    ssize_t n = ::write(fd, peek(), readableBytes());
    if(n < 0)
    {
        *saveErrno = errno;
    }
    return n;
}

这就是调用了系统的write函数。

iovec结构体定义

#include <sys/uio.h>
 
struct iovec {
    ptr_t iov_base; /* Starting address */
    size_t iov_len; /* Length in bytes */
};

struct iovec定义了一个向量元素。通常,这个结构用作一个多元素的数组。对于每一个传输的元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是readv所接收的数据或是writev将要发送的数据。成员iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度。

代码地址:https://github.com/Cheeron955/mymuduo/tree/master

好了~ 有关于muduo库Buffer类的细节就到此结束了,Buffer的设计思想还是很巧妙的,在我们之后用到缓冲区的地方都可以借鉴。接下来我们会介绍muduo库的TcpConnection类,我们下一节见~~

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

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

相关文章

QTreeView学习 branch 虚线设置

1、方法一&#xff1a; #include <QStyleFactory> ui.treeView->setStyle(QStyleFactory::create("windows")); 2、方法二&#xff1a; QString strtyle2 R"( QTreeView::branch:has-siblings:!adjoins-item { border-image: url(:/TreeViewDe…

docker+nginx+Jenkins自动构建

文章目录 前言一、实操记录问下AI&#xff1a;jenkins 配置新增一个mobilegit配置Build TriggersBuild EnvironmentBuild StepsPost-build Actions 上面一顿配置下来&#xff0c;构建 -- FAILURE 总结 前言 在已有docker-Jenkins-nginx 部署方案上&#xff0c;在另外一台测试…

KAN神经网络简短介绍

KANs简介 Kolmogorov-Arnold Networks (KANs) 是一种创新的神经网络模型&#xff0c;它挑战了传统多层感知器(MLPs)的设计&#xff0c;通过将激活函数从节点转移到边上来提升模型的性能和可解释性。KAN的核心在于&#xff0c;其所有权重参数均被单变量的样条函数代替&#xff…

C++(week3):C语言文件操作

文章目录 (十二) 文件1.流(1)流模型(2)程序员视角的文件(3)缓冲区类型(4)标准流(5)二进制文件 与 文本文件(6)文件流的接口(API) 2.打开/关闭文件(1)fopen(2)fclose(3)示例代码 3.读/写文件(1)fgetc / fputc&#xff1a;一个字符一个字符地读写(2)fgets / fputs&#xff1a;一行…

pytest + yaml 框架 - 录制接口转 yaml 用例实现

pytest yaml 框架基本不用写 python 代码&#xff0c;只需写yaml 文件用例就能实现接口自动化。 现在引入接口录制功能&#xff0c;连 yaml 文件也不用写了&#xff0c;点点点就能生成 yaml 用例文件了。 录制功能在v1.3.4版本上实现 pip instal pytest-yaml-yoyo 环境准备 …

string类的介绍与使用【C++】

string类 前言一、为什么学习string类C语言中的字符串示例 二、标准库中的string类string类string类的常用接口说明string类对象的常见构造string类对象的容量操作string的接口测试及使用string类对象的访问及遍历操作下标和方括号遍历范围for遍历迭代器遍历相同的代码&#xf…

Seaborn : 超好用的Python可视化工具

1. 引言 说到数据可视化&#xff0c;Seaborn就像一颗隐藏的宝石&#xff01;在进行探索性数据分析时&#xff0c;我们通常从Matplotlib 开始&#xff0c;而对 Seaborn 的探索相对较少&#xff01;但是&#xff0c;只要你了解 Seaborn 的全部潜力&#xff0c;你就会惊奇地发现&…

半小时搞懂STM32面经知识点——IIC

1.IIC 1.1什么是IIC&#xff1f; 同步半双工通信协议&#xff0c;适用于小数据和短距离传输。 1.2 IIC需要几条线&#xff1f; IIC总共有2条通信总线&#xff08;SDA,SCL&#xff09;&#xff0c;SCL为时钟同步线&#xff0c;用于主机和从机间数据同步操作&#xff1b;SDA为…

Qt开发常见报错大全与解决办法

下面的报错是我日常开发经常遇到的,对着下面的解决方法一招搞定就行了。 我们没必要都去记住,只需要见方抓药即可。 目前版本有27个常见报错,持续更新中。 常见报错 翻译不起作用 你可能改了类名字,但是.ts文件里没有跟着改。 Cannot send events to objects owned by a…

多线程-线程安全

目录 线程安全问题 加锁(synchronized) synchronized 使用方法 synchronized的其他使用方法 synchronized 重要特性(可重入的) 死锁的问题 对 2> 提出问题 对 3> 提出问题 解决死锁 对 2> 进行解答 对4> 进行解答 volatile 关键字 wait 和 notify (重要…

线下研讨会 技术沙龙|乐鑫芯片与 ESP RainMaker® 为科技初创企业赋能

众多科技初创企业在智能硬件市场迅猛发展的背景下&#xff0c;对不断变化的需求展现出了高度的敏锐性&#xff0c;期望能够快速将其转化为切实的产品方案。然而&#xff0c;面对复杂繁重的软硬件集成任务&#xff0c;这些企业往往容易陷入研发瓶颈、资金短缺以及效率低下等多重…

Mybatis技术内幕-基础支撑层

整体架构 MyBatis 的整体架构分为三层&#xff0c; 分别是基础支持层、核心处理层和接口层。 基础支持层 基础支持层包含整个MyBatis 的基础模块&#xff0c;这些模块为核心处理层的功能提供了良好的支撑。 解析器模块 XPathParser MyBatis提供的XPathParser 类封装了XPat…

HackMyVM-Minimal

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 gobuster 文件包含漏洞 提权 web信息收集 main方法 question_1 question_2 question_3 prize.txt 软连接 信息收集 arp ┌──(root?0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: E…

centos7.9系统安全加固

1、限制用户登陆 vim /etc/hosts.deny&#xff0c;若禁止192.168.0.158对服务器进行ssh的登陆&#xff0c;添加如下内容 sshd : 192.168.0.158 添加完毕后就生效了&#xff0c;直接用192.168.0.158访问主机&#xff0c;就无法连接了&#xff0c;显示 Connection closing...Soc…

pycharm报错Process finished with exit code -1073740791 (0xC0000409)

pycharm报错Process finished with exit code -1073740791 (0xC0000409) 各种垃圾文章&#xff08;包括chatgpt产生的垃圾文章&#xff09;&#xff0c;没有给出具体的解决办法。 解决办法就是把具体报错信息显示出来&#xff0c;然后再去查。 勾选 然后再运行就能把错误显示…

图像分割各种算子算法-可直接使用(Canny、Roberts、Sobel)

Canny算子&#xff1a; import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg cv.imread("../test_1_1.png") edges cv.Canny(img, 100, 200)plt.subplot(121),plt.imshow(img,cmap gray) plt.title(Original Image), plt.xticks([]), …

vue2+swiper——实现多图轮播+层叠轮播——技能提升

今天看到同事在写轮播图&#xff0c;由于是jq的写法&#xff0c;我没有过多参与&#xff0c;我只写vue的部分。。。虽然语言不一样&#xff0c;但是用法还是要会的。下面介绍通过swiper组件来实现轮播效果。 解决步骤1&#xff1a;安装swiper npm install swiper5.4.5 我这边…

数据分享—全国分省河流水系

河流水系数据是日常研究中必备的数据之一&#xff0c;本期推文主要分享全国分省份的水系和河流数据&#xff0c;梧桐君会不定期的更新数据&#xff0c;欢迎长期订阅。 数据预览 山东省河流水系 吉林省河流水系 四川省河流水系 数据获取方式 链接&#xff1a;https://pan.baidu.…

基于阿里云向量检索 Milvus 版与 PAI 搭建高效的检索增强生成(RAG)系统

阿里云向量检索 Milvus 版现已无缝集成于阿里云 PAI 平台&#xff0c;一站式赋能用户构建高性能的检索增强生成&#xff08;RAG&#xff09;系统。您可以利用 Milvus 作为向量数据的实时存储与检索核心&#xff0c;高效结合 PAI 和 LangChain 技术栈&#xff0c;实现从理论到实…

网络基础(三)——网络层

目录 IP协议 1、基本概念 2、协议头格式 2.1、报头和载荷如何有效分离 2.2、如果超过了MAC的规定&#xff0c;IP应该如何做呢&#xff1f; 2.3、分片会有什么影响 3、网段划分 4、特殊的ip地址 5、ip地址的数量限制 6、私有ip地址和公网ip地址 7、路由 IP协议 网络…