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

接着之前我们[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类,我们下一节见~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

十四、Redis Cluster集群

Redis Cluster是Redis提供的一个分布式解决方案&#xff0c;在3.0推出。Redis Cluster可以自动将数据分片分布到不同的master节点上&#xff0c;同时提供了高可用的支持&#xff0c;当某个master节点挂了之后&#xff0c;整个集群还是可以正常工作。1、为什么要用Redis Cluster…

Kafka 业务日志采集最佳实践

简介 Apache Kafka 是一个分布式流处理平台&#xff0c;主要用于构建实时数据流管道和应用程序。在收集业务日志的场景中&#xff0c;Kafka 可以作为一个消息中间件&#xff0c;用于接收、存储和转发大量的日志数据。将 Kafka 与其他系统&#xff08;如 Elasticsearch、Flume、…

AtCoder Beginner Contest 353 A~E(F,G更新中...)

A.Buildings 题意 给出若干个建筑&#xff0c;每个建筑有一个高度&#xff0c;问&#xff0c;从第二个建筑开始&#xff0c;比第一个建筑高的建筑中编号最小的是多少&#xff1f;如果不存在&#xff0c;输出-1. 分析 边输入边比较即可&#xff0c;如果循环结束还未找到&…

【muzzik 分享】Cocos 物理帧同步

# 前言 之前没研究帧同步&#xff0c;这是我前端时间没上班时边玩边搞做的 Demo 研究成果&#xff0c;总共时间一周&#xff08;实际2-3天&#xff09;&#xff0c;发布的目的也很简单&#xff0c;打破技术垄断&#xff0c;才能诞生更高端的技术成果。而且就算我没发这篇帖子&…

重生奇迹mu战士攻略有哪些

1、生命之光&#xff1a;PK前起手式&#xff0c;增加血上限。 2、雷霆裂闪&#xff1a;眩晕住对手&#xff0c;战士PK战士第一技能&#xff0c;雷霆裂闪是否使用好关系到胜负。 3、霹雳回旋斩&#xff1a;雷霆裂闪后可以选择用霹雳回旋斩跑出一定范围(因为对手下一招没出意外…

Mybatis技术内幕-基础支撑层

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

SpringCloud使用Nacos作为配置中心实现动态数据源切换

一、Nacos-Server 了解Nacos可以直接阅读官方文档 使用Nacos&#xff0c;我们需要有Nacos-Server&#xff0c;此处就不使用官方提供的release版本了&#xff0c;而是自己编译&#xff0c;因为本来就是Java开发的&#xff0c;所以对于Javaer来说也没啥难度&#xff01; git c…

记nrm管理仓库以及发布npm包

前言 记一次在公司创建私有库以及发布npm包&#xff0c;留下个脚印 一、nrm是什么&#xff1f; nrm是 npm 镜像源管理工具&#xff0c;用于快速地在不同的 npm 源之间切换。 二、使用步骤 1.全局安装nrm 代码如下&#xff08;示例&#xff09;&#xff1a; npm install -…

Git系列:git diff使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【智能算法】鹭鹰优化算法(SBOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;Y Fu受到自然界中鹭鹰生存行为启发&#xff0c;提出了鹭鹰优化算法&#xff08;Secretary Bird Optimization Algorithm, SBOA&#xff09;。 2.算法原理 2.1算法思想…

Elasticsearch查看集群信息,设置ES密码,Kibana部署

Elasticsearch查看集群信息&#xff0c;设置ES密码&#xff0c;Kibana部署 查看集群信息查看节点信息查看集群健康状态查看分片信息查看其他集群信息 Kibana部署安装设置ES密码 查看集群信息 查看节点信息 curl http://127.0.0.1:9200/_cat/nodes?v 参数说明&#xff1a; ip…

OpenGL导入的纹理图片错位

在OpenGL中导入图片的纹理照片的函数为 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, p_w, p_h, 0, GL_BGR, GL_UNSIGNED_BYTE, pic_data);其中p_w, p_h为图片的宽和高&#xff0c;pic_data为指向图片存储空间的的地址(unsigned char *类型) 在OpenGL中图片默认是4字节对齐的&…

Web开发小知识点(二)

1.关于取余 我在Dart语言里&#xff08;flutter项目&#xff09; int checkNum (10 - 29) % 10; 那么checkNum等于1 但是在Vue项目里 const checkNum (10 - 29) % 10;却等于-9 语言的特性不同&#xff0c;导致结果也不同&#xff0c;如果要想和Dart保持一致&#xff0c;…

vue3.0(六) toRef,toValue,toRefs和toRow,markRaw

文章目录 toReftoValuetoRefstoRowmarkRawtoRef和toRefs的区别toRaw 和markRaw的用处 toRef toRef 函数可以将一个响应式对象的属性转换为一个独立的 ref 对象。返回的是一个指向源对象属性的 ref 引用&#xff0c;任何对该引用的修改都会同步到源对象属性上。使用 toRef 时需…

[蓝桥杯 2021 国 ABC] 123(java)——前缀和,思维

目录 题目 解析 代码 这么久了&#xff0c;我终于能不看别人代码完整写出来了&#xff0c;呜呜呜。虽然过程也是很曲折。 题目 解析 这个题&#xff0c;找其中数列的规律&#xff0c;1,1,2,1,2,3,1,2,3,4&#xff0c;...&#xff0c;因此我们把拆分成行列&#xff0c;如下…

Java并发处理

Java并发处理 问题描述:项目中业务编号出现重复编号 生成编号规则&#xff1a;获取数据库表最大值&#xff0c;然后再做1处理&#xff0c;即为新编号&#xff08;因为起始值是不固定的&#xff0c;还存在‘字符数据’格式&#xff0c;做了字典项可配置&#xff0c;所以不能直…

Ai 一键美术绘画文章,蓝海项目,流量巨大,盈利成效显著

今天我要向大家介绍一个全新的蓝海项目&#xff0c;那就是AI一键美术绘画文章。这个项目打破了传统的思维模式&#xff0c;更加吸引人的眼球&#xff0c;已经在各大网站上引发了大量的关注&#xff0c;轻松在抖音上热门也变得简单易行且稳定。 下载 地 址 &#xff1a; laoa…

PopClip for Mac 激活版:让文本处理更高效

还在为繁琐的文本处理而烦恼吗&#xff1f;PopClip for Mac来帮您解决&#xff01;这款神器般的文本处理工具&#xff0c;能让您轻松应对各种文本处理任务。无论是写作、编程还是日常办公&#xff0c;PopClip for Mac都能助您一臂之力&#xff0c;让您的文本处理更高效、更便捷…

【基础绘图】 09.小提琴图

效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;生成随机数组 2. 数据处理&#xff1a;计算四分位数、中位数、均值、最大最小值 3. 图像绘制&#xff1a;绘制小提琴图 详细代码&#xff1a;着急的直接拖到最后有完整代码 步骤一&#xff1a;导入库包及…

动态规划----股票买卖问题(详解)

目录 一.买卖股票的最佳时机&#xff1a; 二.买卖股票的最佳时机含冷冻期&#xff1a; 三.买卖股票的最佳时期含⼿续费&#xff1a; 四.买卖股票的最佳时机III: 五.买卖股票的最佳时机IV: 买卖股票的最佳时机问题介绍&#xff1a;动态规划买卖股票的最佳时机是一个经典的…