缓冲区Buffer类的设计(参考Muduo实现)

news2024/11/22 13:53:16

Buffer的功能需求:

Buffer类的设计目的是再创造一层应用层缓冲区。                                                                         

其对外表现为一块连续的内存(char* p, int len),以方便客户代码的编写。

size() 可以自动增长,以适应不同大小。

内部以 std::vector<char>来保存数据,并提供相应的访问函数。

Buffer类的结构:

 Buffer类有三个成员变量,一个 std::vector<char>,和两个size_t的readerIndex,writerIndex用来表示读写的位置。

 构造函数:

Buffer::Buffer(size_t initialSize):buffer(initialSize),readerIndex(0),writerIndex(0)
{
    assert(readableBytes() == 0);
    assert(writableBytes() == initialSize);
    assert(prependableBytes() == 0);
}

依据上图结构,得出三个基础的成员函数:

//可读bytes数
size_t Buffer::readableBytes() const
{
    return writerIndex - readerIndex;
}
//可写bytes数
size_t Buffer::writableBytes() const
{
    return buffer.size() - writerIndex;
}
//预留bytes数
size_t Buffer::prependableBytes() const
{
    return readerIndex;
}

此外,buffer类应模仿平时使用的容器,提供访问数据元素的迭代器

    char* begin() 
    {
        return &*buffer.begin();
    }
    //常量默认调用此重载函数
    const char* begin() const 
    {
        return &*buffer.begin();
    }

    char* Buffer::beginWrite()
    {
        return begin() + writerIndex;
    }
    //常量默认调用此重载函数
    const char* Buffer::beginWrite() const
    {
        return begin() + writerIndex;
    }

这里提供两个重载,一个由变量调用,返回可修改内部元素的可写迭代器;

一个由常量调用,返回不可修改内部元素的只读迭代器。 

还应提供可以直接访问CONTENT区域的迭代器:

const char* Buffer::peek() const
{
    return begin() + readerIndex;
}

向buffer中写入数据和从buffer中读出数据都需要修改readerIndex与writerIndex的大小

//向buffer中写入len bytes数据
void Buffer::hasWritten(size_t len)
{
    writerIndex += len;
}
//撤销向buffer中写入的len bytes数据
void Buffer::unwrite(size_t len)
{
    assert(len <= readableBytes());
    writerIndex -= len;
}

//从buffer中读取len bytes数据
void Buffer::retrieve(size_t len)
{
    assert(len <= readableBytes());
    if(len<readableBytes())
    {
        readerIndex += len;
    }
    else//len == readableBytes(),全部读取
    {
        retrieveAll();
    }
}
//从buffer中读取全部可读数据
void Buffer::retrieveAll()
{
    bzero(&buffer[0], buffer.size());
    readerIndex = 0;
    writerIndex = 0;
}
//读至指定结尾end
void Buffer::retrieveUntil(const char* end)
{
    assert(peek() <= end);
    assert(end <= beginWrite());
    retrieve(end - peek());
}

获得从buffer中读出数据的副本

//读取全部可读数据
std::string Buffer::retrieveAllAsString()
{
    return retrieveAsString(readableBytes());
}
//读取指定字节数
std::string Buffer::retrieveAsString(size_t len)
{
    assert(len <= readableBytes());
    std::string res(peek(), len);
    retrieve(len);
    return res;
}

扩大buffer的大小:

buffer的 size() 可以自动增长,以适应不同大小。而有时候,经过若干次读写,readIndex 移到了比较靠后的位置,留下了巨大的 prependable 空间。Buffer 在这种情况下不会重新分配内存,而是先把已有的数据移到前面去,腾出 writable 空间

    void makeSpace(size_t len)
    {
        //剩余空间不足len,则直接扩大buffer大小
        if (writableBytes() + prependableBytes() < len)
        {
            buffer.resize(writerIndex + len + 1);
        }
        else//剩余空间大于等于len,则移动可读数据至最前面,腾出空间
        {
            size_t readable = readableBytes();
            std::copy(begin() + readerIndex, begin() + writerIndex, begin());
            readerIndex = 0;
            writerIndex = readerIndex + readable;
            assert(readable == readableBytes());
        }
    }

向buffer中直接添加新的数据:

//添加string类型数据
void Buffer::append(const std::string& str)
{
    append(str.data(), str.length());//转至c风格字符串
}
//添加c风格字符串
void Buffer::append(const char* str, size_t len)
{
    ensureWriteableBytes(len);//确保buffer的可写大小足够添加新数据
    std::copy(str, str + len, beginWrite());
    hasWritten(len);
}
//添加void*类型字串
void Buffer::append(const void* data, size_t len)
{
    append(static_cast<const char*>(data), len);
}

向buffer中添加从套接字fd中读取的数据:

这里为了实现线程安全,选择传入 值—结果参数int *Errno;

此外,由于希望减少系统调用,一次读的数据越多越划算,那么似乎应该准备一个大的缓冲区,而另一方面,我们又希望系统减少内存占用。Buffer类使用 readv(分散读) 结合栈区空间,解决了这个问题。

具体做法是:在栈上准备一个 65536 字节的 extrabuf,然后利用 readv() 来读取数据,iovec 有两块,第一块指向 Buffer 中的 writable 字节,另一块指向栈上的 extrabuf。这样如果读入的数据不多,就全部都读到 Buffer 中去了;如果长度超过 Buffer 的 writable 字节数,就会读到栈上的extrabuf 里,然后再把 extrabuf 里的数据 append 到 Buffer 中。

ssize_t Buffer::readFd(int fd, int* Errno)
{
    char extrabuf[65536];
    struct iovec vec[2];
    const size_t writable = writableBytes();
    //分散读,buffer内的 writable字节(堆区)+ 固定的 extrabuf(栈区)
    vec[0].iov_base = begin() + writerIndex;
    vec[0].iov_len = writable;
    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof(extrabuf);
    //如果writable已经很大了,就无需将第二块内存分配出去
    const int iovcnt = (writable < sizeof(extrabuf)) ? 2 : 1;
    const ssize_t len = readv(fd, vec, iovcnt);
    if (len < 0)
    {
        *Errno = errno;
    }
    else if (static_cast<size_t>(len) <= writable)//长度未超过buffer的writable字节数
    {
        writerIndex += len;
    }
    else//长度超过buffer的writable字节数
    {
        writerIndex = buffer.size();
        append(extrabuf, len - writable);
    }
    return len;
}

Buffer.h         (Buffer类内的声明均在上述描述中给出定义)

class Buffer 
{
public:
    Buffer(size_t initialSize = 1024);
    ~Buffer() = default;

    size_t readableBytes() const;
    size_t writableBytes() const;
    size_t prependableBytes() const;

    const char* peek() const;
    char* beginWrite();
    const char* beginWrite() const;
    void ensureWriteableBytes(size_t len);
    void hasWritten(size_t len);
    void unwrite(size_t len);
    
    void retrieve(size_t len);
    void retrieveUntil(const char* end);
    void retrieveAll();
    std::string retrieveAllAsString();
    std::string retrieveAsString(size_t len);

    void append(const std::string& str);
    void append(const char* str, size_t len);
    void append(const void* data, size_t len);

    ssize_t readFd(int fd, int* Errno);
private:
    char* begin() 
    {
        return &*buffer.begin();
    }
    //常量默认调用此重载函数
    const char* begin() const 
    {
        return &*buffer.begin();
    }
    void makeSpace(size_t len);

    std::vector<char> buffer;
    size_t readerIndex;
    size_t writerIndex;
};

参考资料:

Muduo 设计与实现之一:Buffer 类的设计_陈硕的博客-CSDN博客

http://code.google.com/p/muduo/source/browse/trunk/muduo/net/Buffer.cc#36

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

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

相关文章

Java如何自定义一个变长数组?

文章目录思路分析实现代码测试结果首先需要声明的是&#xff0c; Java本身是提供了变长数组的&#xff0c;即 ArrayList。那么自定义一个变长数组有啥用&#xff1f;其实没啥用或者说用处不大&#xff0c;主要就是为了了解下变长数组的设计理念而已。实际工作中直接使用 ArrayL…

文华财经期货背离信号准确率高指标公式,单边趋势行情增仓上行多空共振策略

由“短线震荡波段王”和“三柱共振-高把握”指标合成 功能 : 1.红绿小波段黄蓝中波段粉青大波段 2.红绿中小波段 3.顶背底背提示 4.金叉死叉提示 5.多和空提示-金叉死叉改写 优点:宽幅震荡和窄幅震荡 弊端:单边行情(可结合多空趋势主图规避) 功能: 1.红绿波段 2.大中量仓-单…

MySQL索引详解

目录 1、为什么要有索引&#xff1f; 2、预备知识 3、为何IO交互要是 Page&#xff1f; 4、如何理解Page以及索引理解 5、索引操作 <1> 创建主键索引 <2> 创建唯一索引 <3> 普通索引的创建 <4> 全文索引的创建 <5> 查询索引 <5>…

Python图像识别实战(五):卷积神经网络CNN模型图像二分类预测结果评价(附源码和实现效果)

前面我介绍了可视化的一些方法以及机器学习在预测方面的应用&#xff0c;分为分类问题&#xff08;预测值是离散型&#xff09;和回归问题&#xff08;预测值是连续型&#xff09;&#xff08;具体见之前的文章&#xff09;。 从本期开始&#xff0c;我将做一个关于图像识别的…

BOOT进程控制模式与故障排错

1. BOOT reboot and shutdown—使用systemctl 命令。 systemctl poweroff–关机 systemctl reboot --重启 systemctl halt 禁用CPU 在7版本中使用systemctl 工具。 选择systemd target graphical.target 桌面图形模式 multi-user.target 多用户模式–命令行 rescue.target 救援…

Linux驱动开发基础__总线设备驱动模型

目录 1 驱动编写的三种方法 1.1 传统写法 1.2 总线设备驱动模型 1.3 设备树 2 在 Linux 中实现“分离”&#xff1a;Bus/Dev/Drv 模型 3 匹配原则 4 函数调用关系 1 驱动编写的三种方法 1.1 传统写法 1.2 总线设备驱动模型 引入platform_device、platform_driver&…

二叉数题型2

目录 二叉搜索树的众数 二叉树的最近公共祖先 修剪二叉树 二叉搜索树的众数 问题描述&#xff1a; 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#…

PROJ 9.1.1源码下载编译(Win10+VS2022)

目录PROJ什么是PROJPROJ下载方式资源结构编译PROJ打包编译成功的库PROJ 什么是PROJ Proj是一个免费的GIS工具。 它专注于地图投影的表达&#xff0c;以及转换。采用一种非常简单明了的投影表达PROJ&#xff0c;比其它的投影定义简单&#xff0c;但很明显。很容易就能看到各种…

无人机倾斜摄影测量技术的优势有哪些?

传统的地理信息获取工作一般是通过人工测量的方式进行&#xff0c;但这样的测量方式具有工作强度大、成本高等问题。随着现代科技的不断发展&#xff0c;测绘行业对地理信息数据的准确性、时效性要求也越来越高&#xff0c;人工成本和时间成本也为行业带来了巨大的压力。因此&a…

GIT回退到指定版本的两种方法(reset/revert)

实现多人合作程序开发的过程中&#xff0c;我们有时会出现错误提交的情况&#xff0c;此时我们希望能撤销提交操作&#xff0c;让程序回到提交前的样子&#xff0c;本文总结了两种解决方法&#xff1a;reset、revert。 命令特点reset该命令会强行覆盖当前版本和要回退的版本之…

ArcGIS基础实验操作100例--实验15设置字段属性域

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验15 设置字段属性域 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&a…

如何用Sonic云真机打王者

使用Sonic进行跨网段部署&#xff0c;助力海外业务的公司进行专项检测。提供定时任务充分利用无人值守时间回归UI测试&#xff0c;省时省力。自研随机事件测试与UI遍历测试&#xff0c;支持打通Jenkins的DevOps流程&#xff0c;Sonic提供图像识别&#xff0c;后续还会添加poco控…

ECS-弹性容器服务 - Part 2

68-ECS-弹性容器服务 - Part 2 Hello大家好&#xff0c;我们今天继续ECS的内容。 Service load balancing 之前的课时讨论过&#xff0c;在ECS集群上创建的ECS服务支持AWS负载均衡器&#xff0c;而应用程序负载均衡器和ECS服务通常是一个很好的搭配&#xff0c;因为应用程序负…

Docker 基础概念介绍

一 什么是 docker &#xff1f; Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;…

【nowcoder】笔试强训Day13

目录 一、选择题 二、编程题 2.1参数解析 2.2跳石板 一、选择题 1.一个关系数据库文件中的各条记录 &#xff08;&#xff09; 。 A. 前后顺序不能任意颠倒&#xff0c;一定要按照输入的顺序排列 B. 前后顺序可以任意颠倒&#xff0c;不影响库中的数据关系 C. 前后顺序…

前端面试题之计算机网络篇--HTTP协议

HTTP协议 1. GET和POST的请求的区别 GET和POST方法 GET和POST方法都是HTTP中的方法 什么是 HTTP&#xff1f; 超文本传输协议&#xff08;Hypertext Transfer Protocol&#xff0c;缩写 HTTP&#xff09;旨在启用客户端和服务器之间的通信。 HTTP 充当客户端和服务器之间的…

Android进阶——Javac编译解析

Javac编译器 1.Javac的源码与调试 Javac的源码下载地址&#xff1a;Javac的源码下载地址&#xff0c;在Myeclipse中新建项目Compiler_javac&#xff0c;把源码复制到项目中。 Javac的源码目录&#xff1a; 从Sun Javac的代码来看&#xff0c;编译过程大致可以分为3个过程&…

测试工程师正遭「革命」 AI将改写测试模式

文章目录❤️‍&#x1f525; 软件测试的现状❣️ 功能测试的短板❣️ 过于的依赖工具❤️‍&#x1f525; 测试行业的两极分化❤️‍&#x1f525; 纯功能测试人员应该如何破局❣️ 龙测 AI TestOps 云平台❣️ AI TestOps 亮相 TICA❣️ AI TestOps 所实现的混合模型解决方案…

相关系数(皮尔逊pearson相关系数和斯皮尔曼spearman等级相关系数)

目录 总体皮尔逊Person相关系数&#xff1a; 样本皮尔逊Person相关系数&#xff1a; 两点总结&#xff1a; 假设检验&#xff1a;&#xff08;可结合概率论课本假设检验部分&#xff09; 皮尔逊相关系数假设检验&#xff1a; 更好的方法&#xff1a;p值判断方法 皮尔逊相…

lua调用c动态库实例

简介 Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。 特点 轻量级: 它用标准C语言编写并以源代码形式开放&#xff0c;编译后仅仅一百余K&a…