【Muduo】缓冲区Buffer类

在 Muduo 网络库中,Buffer类用于处理网络 I/O 中的数据缓冲。防止应用程序读写太快而网络链路收发速度慢导致的速度不匹配问题。这个类封装了一个内部缓冲区(使用了vector<char>),并提供了一系列方法来操作这个缓冲区,如读取、写入、扩容等。

设计目标

Buffer类的设计目标主要有以下几点:

  1. 高性能:通过减少内存拷贝和分配次数来提高性能。
  2. 易用性:提供简洁的API来方便地进行数据的读写操作。
  3. 灵活性:支持动态扩容,以适应不同大小的数据。

主要成员变量

  • std::vector<char> buffer_:存储数据的内部缓冲区。
  • size_t readerIndex_:指向可读数据的起始位置。
  • size_t writerIndex_:指向可写区域的下一个位置。
  • static const size_t kCheapPrepend = 8:预留的前置空间大小,用于优化小数据的写入。
  • static const size_t kInitialSize = 1024:初始缓冲区大小。

缓冲区结构

/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
/// @code
/// +-------------------+------------------+------------------+
/// | prependable bytes |  readable bytes  |  writable bytes  |
/// |      缓冲区头     |     (CONTENT)    |     可写空间     |
/// +-------------------+------------------+------------------+
/// |                   |                  |                  |
/// 0      <=      readerIndex   <=   writerIndex    <=     size
/// @endcodestatic const size_t kCheapPrepend = 8;   // 缓冲区头长度
static const size_t kInitialSize = 1024; // 默认初始化大小

设计思想

Buffer类的设计思想主要体现在以下几个方面:

  1. 双指针设计:使用readerIndex_writerIndex_两个指针来分别追踪可读数据和可写区域的位置,实现了读写区域的分离。
  2. 预留空间:通过预留一定的前置空间(kCheapPrepend),可以减少小数据写入时的内存移动操作,提高性能。
  3. 动态扩容:当可写空间不足时,Buffer类会自动进行扩容,以满足写入需求。

重要成员函数

  1. 构造函数:初始化Buffer对象,设置初始大小和指针位置。
  2. readableBytes():返回可读字节数。
  3. writableBytes():返回可写字节数。
  4. prependableBytes():返回预留空间的大小。
  5. peek():返回指向可读数据的指针。
  6. retrieve(size_t len):将已读数据从缓冲区中移除,并更新readerIndex_
  7. retrieveAll():移除所有已读数据,重置指针位置。
  8. append(const char data, size_t len):将数据追加到缓冲区末尾,并更新writerIndex_
  9. ensureWritableBytes(size_t len):确保至少有len字节的可写空间,如果不足则进行扩容。
  10. makeSpace(size_t len):根据需要扩容或移动数据以腾出足够的可写空间。

扩容策略

Buffer类的扩容策略是其设计的一个亮点。当可写空间不足时,它首先检查整个缓冲区(包括预留空间和已读数据区域)是否足够容纳新的数据。如果足够,它会通过移动数据来腾出空间,而不是直接扩容。这样可以减少不必要的内存分配和拷贝操作,提高性能。只有当整个缓冲区都不足时,它才会进行扩容。

readFd和writeFd方法

  • readFd(int fd, int saveErrno):从文件描述符fd中读取数据到Buffer中。这个方法使用了readv系统调用来提高读取性能,特别是在读取大量数据时。它首先尝试将数据读取到Buffer的可写区域中,如果不够则使用额外的栈缓冲区来存储剩余的数据。最多可以读取 128k-1 个字节。​​​​​​
  • writeFd(int fd, int saveErrno):将Buffer中的数据写入到文件描述符fd对应的发送缓冲区中。这个方法使用了write系统调用来发送数据。它直接将指向可读数据的指针传递给write函数,避免了不必要的内存拷贝。

源码

Buffer.h
#pragma once#include <vector>
#include <string>/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
///                      模仿
/// @code
/// +-------------------+------------------+------------------+
/// | prependable bytes |  readable bytes  |  writable bytes  |
/// |      缓冲区头     |     (CONTENT)    |     可写空间     |
/// +-------------------+------------------+------------------+
/// |                   |                  |                  |
/// 0      <=      readerIndex   <=   writerIndex    <=     size
/// @endcode// 网络库底层的缓冲区类型
class Buffer
{
public:static const size_t kCheapPrepend = 8;   // 缓冲区头长度static const size_t kInitialSize = 1024; // 默认初始化大小Buffer(size_t initialSize = kInitialSize): buffer_(kCheapPrepend + initialSize),readerIndex_(kCheapPrepend),writerIndex_(kCheapPrepend){}~Buffer() = default;// writerIndex_ - readerIndex_size_t readableBytes() const{return writerIndex_ - readerIndex_;}// buffer_.size() - writerIndex_size_t writableBytes() const{return buffer_.size() - writerIndex_;}// return readerIndex_size_t prependableBytes() const{return readerIndex_; // 随着上层的读取,readerIndex_会变}// 返回第一个可读的数据地址const char *peek() const{// char * + readerIndex_return begin() + readerIndex_;}// 将readerIndex_向后移动已经读走的len长度的字节void retrieve(size_t len){if (len < readableBytes())// 长度小于可读大小{readerIndex_ += len;}else // 否则即为读取所有{retrieveAll();}}void retrieveAll(){readerIndex_ = kCheapPrepend;writerIndex_ = kCheapPrepend;}std::string retrieveAllAsString(){return retrieveAsString(readableBytes());}std::string retrieveAsString(size_t len){std::string result(peek(), len); // 第一个可读数据的地址开始的len长度retrieve(len);return result;}/// @brief 从原位置data处开始的len个字节数据追加到缓冲区中/// @param data 源数据位置/// @param len 要追加的字节个数void append(const char* /*restrict*/ data, size_t len){ensureWritableBytes(len); // 确保容量足够std::copy(data, data+len, beginWrite());writerIndex_ += len;}void append(const void* /*restrict*/ data, size_t len){append(static_cast<const char*>(data), len);}void ensureWritableBytes(size_t len){if (writableBytes() < len){makeSpace(len);}}char* beginWrite(){return begin() + writerIndex_;}const char* beginWrite() const{ return begin() + writerIndex_; }// 通过fd读取数据:从fd中read数据,写到缓冲区中的writable区域ssize_t readFd(int fd, int* savedErrno);// 通过fd发送数据:将buffer的readable区域的数据,写入到fd中ssize_t writeFd(int fd, int* savedErrno);private:char* begin(){ return &*buffer_.begin(); }const char *begin() const{// *buffer_.begin()获取到vector第一个元素,使用&取地址return &*buffer_.begin();}// buffer扩容void makeSpace(size_t len){// 若可写大小 + 0~可读位置 不够 要写的数据长度,则申请空间if (writableBytes() + prependableBytes() < len + kCheapPrepend){// FIXME: move readable databuffer_.resize(writerIndex_+len);}else // 否则不申请空间,将未读的数据向前移动到紧挨包头之后{// move readable data to the front, make space inside buffersize_t readable = readableBytes(); // 还未读的数据长度std::copy(begin()+readerIndex_,begin()+writerIndex_,begin()+kCheapPrepend); // 紧挨包头之后readerIndex_ = kCheapPrepend;writerIndex_ = readerIndex_ + readable;}}std::vector<char> buffer_;size_t readerIndex_;size_t writerIndex_;
};
Buffer.cc
#include "Buffer.h"
#include <sys/uio.h>
#include <sys/socket.h>
#include <unistd.h>// 
/*** 从fd中read数据,写到缓冲区中的writable区域* Poller工作在LT模式,数据不会丢* Buffer缓冲区有大小,但是从fd读数据的时候不知道tcp数据最终大小* 所以使用 iovec + extrabuf 使用额外空间
*/
ssize_t Buffer::readFd(int fd, int *savedErrno)
{char extrabuf[65536] = {0}; // 栈上64k额外数据空间,防止读fd的时候buffer_数组不够用/*** struct iovec* {*    void *iov_base;	// Pointer to data.  *    size_t iov_len;	// Length of data.  *  };* 将iovcnt个iovec传入ssize_t readv(int fd, const struct iovec *iov, int iovcnt);* 可以在从fd读取的时候,顺序依次写入每个iovec中*/iovec vec[2]; // 创建两个iovecconst size_t writable = writableBytes(); // Buffer底层缓冲区剩余的可写大小vec[0].iov_base = begin() + writerIndex_;vec[0].iov_len = writable;vec[1].iov_base = extrabuf;vec[1].iov_len = sizeof extrabuf;// when there is enough space in this buffer, don't read into extrabuf.// when extrabuf is used, we read 128k-1 bytes at most.const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; const ssize_t n = ::readv(fd, vec, iovcnt);if(n < 0){*savedErrno = errno;}else if(n < writable){ // Buffer底层缓冲区剩余的可写大小 已经足够writerIndex_ += n;}else{ // 使用了extrabuf,需要将其移动到Buffer中writerIndex_ = buffer_.size(); // buffer_已经写满// 将extrabuf中数据移动到buffer_中append(extrabuf, n - writable);}return n;
}/*** 将buffer的readable区域的数据,写入到fd中
*/
ssize_t Buffer::writeFd(int fd, int *savedErrno)
{ssize_t n = ::write(fd, peek(), readableBytes());if(n <= 0){*savedErrno = errno;}return n;
}

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

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

相关文章

【Linux网络】端口及UDP

文章目录 1.再看四层2.端口号2.1引入linux端口号和进程pid的区别端口号是如何生成的传输层有了pid还设置端口号端口号划分 2.2问题2.3netstat 3.UDP协议3.0每学一个协议 都要讨论一下问题3.1UDP协议3.2谈udp/tcp实际上是在讨论什么&#xff1f; 1.再看四层 2.端口号 端口号(Po…

//TODO 注释的作用

// TODO 用来标记某处&#xff0c;表示该处含有待办事项&#xff08;尚未解决&#xff09;。其设计本意只是提醒开发者注意&#xff0c;除了代码高亮之外&#xff0c;还可以借助编辑器实现快速定位。 如何使用&#xff1f; 直接在双斜杠后面加 TODO 或者 todo 即可&#xff0c…

CVPR2022医疗图像-GBCNet网络:胆囊癌(GBC)超声(USG)图像检测模型

Surpassing the Human Accuracy:Detecting Gallbladder Cancer from USG Images with Curriculum Learning&#xff1a;超越人类的准确性:基于课程学习的USG图像检测胆囊癌 目录 一、背景与意义 二、介绍 三、网络框架 3.1 区域选择网络 3.2 MS-SoP分类器 3.3 多尺度块 …

抖音运营_抖音电商介绍

截止20年8月&#xff0c;抖音的日活跃数高达6亿。 20年6月&#xff0c;上线抖店 &#xff08;抖音官方电商&#xff09; 一 抖店的定位和特色 1 一站式经营 帮助商家进行 商品交易、店铺管理、客户服务 等全链路的生意经营 2 多渠道拓展 抖音、今日头条、西瓜、抖音火山版…

ASP+ACCESS基于WEB社区论坛设计与实现

摘要&#xff1a;系统主要实现BBS网站全部功能。采用目前应用最为广泛的ASP作为开发工具来开发此系统、以保证系统的稳定性。采用目前最为流行的网页制作工具Dreamweaver和目前最为流行的动画制作工具Flash MX。整个系统从符合操作简便、界面友好、灵活、实用、安全的要求出发&…

首次面试实习岗,有点紧张。。。

首次面试实习岗&#xff0c;有点紧张。。。 文章目录 首次面试实习岗&#xff0c;有点紧张。。。算法1.三角形问题2.一年中第几天问题 提问&#xff1a;一、SpringBoot的配置文件中的数据有哪几种获取方式&#xff0c;分别是怎么获取的&#xff1f;二、Autowire和Resource有什么…

卤菜销售|基于SSM+vue的智能卤菜销售平台的设计与实现(源码+数据库+文档)

智能卤菜销售平台 目录 基于SSM&#xff0b;vue的智能卤菜销售平台的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户功能模块 4商家功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八…

windows查看mysql的版本(三种方法)

方法一&#xff1a;在win r 打开 cmd&#xff0c;在cmd命令状态下&#xff1a;mysql --help 回车即可 方法二&#xff1a;在mysql客户端窗口状态下&#xff1a;输入status并回车即可 在计算机开始菜单搜索以上应用打开即可进入mysql客户端窗口。 方法三&#xff1a;在mys…

【必会面试题】Redis如何实现分布式锁?

目录 一、为什么要使用分布式锁&#xff1f;二、什么是分布式锁&#xff1f;三、什么是Redis分布式锁&#xff1f;四、如何实现Redis分布式锁&#xff1f;1. 基础方法&#xff1a;SETNX EXPIRE2. Lua 脚本3. Redisson 实现4. Redlock 算法 五、JavaRedis分布式锁1. 基于Jedis的…

pikachu-Unsafe Filedownload

任意点击一个图片进行下载&#xff0c;发现下载的url。 http://127.0.0.1/pikachu/vul/unsafedownload/execdownload.php?filenamekb.png 构造payload&#xff1a; 即可下载 当前页面的源码&#xff0c;可以进行路径穿越来下载一些重要的配置文件来获取信息。 http://127.0.…

Android Audio基础——Track构造过程(十)

在 Android 中,Track 通常是指用于播放音频或视频的轨道或通道,用于音频流的控制。在这种语境下,AudioTrack 是用于播放音频的类之一,它提供了直接控制音频数据播放的功能。因此,可以说 AudioTrack 是 Android 中处理音频轨道的一种实现方式之一。 一、Track构造 我们接着…

函数对象引用题目

下列代码的输出是什么&#xff1f; python def func(x): return x1 funcs[func]*5 result[f(0) for f in funcs] print(result) A.[1,1,1,1,1] B.[1,2,3,4,5] C.[0,0,0,0,0] D.[0,1,2,3,4] 3 2 1 答案是A&#xff0c;你答对了吗&#xff1f; 解析&#xff1a; def fu…

前端基础入门三大核心之HTML篇 —— 同源策略的深度解析与安全实践

前端基础入门三大核心之HTML篇 —— 同源策略的深度解析与安全实践 一、同源策略&#xff1a;定义与起源1.1 定义浅析1.2 何为“源”&#xff1f;1.3 起源与意义 二、同源策略的运作机制2.1 限制范围2.2 安全边界 三、跨越同源的挑战与对策3.1 JSONP3.2 CORS3.3 postMessage 四…

VUE 创建组件常见的几种方式

在 Vue.js 中&#xff0c;组件的创建和使用通常遵循以下三种方法&#xff1a; 1. 全局组件 全局组件是通过 Vue.component() 方法创建的&#xff0c;注册后的组件可以在任何新创建的 Vue 实例&#xff08;包括根实例&#xff09;的模板中使用。 Vue.component(my-component,…

【MySQL精通之路】系统变量-持久化系统变量

MySQL服务器维护用于配置其操作的系统变量。 系统变量可以具有影响整个服务器操作的全局值&#xff0c;也可以具有影响当前会话的会话值&#xff0c;或者两者兼而有之。 许多系统变量是动态的&#xff0c;可以在运行时使用SET语句进行更改&#xff0c;以影响当前服务器实例的…

[AI Google] 10个即将到来的Android生态系统更新

新的体验带来了更强的防盗保护、手表电池寿命优化&#xff0c;以及对电视、汽车等的娱乐功能改进。 昨天&#xff0c;我们分享了Android如何以人工智能为核心重新构想智能手机。今天&#xff0c;我们推出了Android 15的第二个测试版&#xff0c;并分享了更多我们改进操作系统的…

java原型模式 (Prototype Pattern) 介绍

原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它通过复制现有的实例来创建新对象&#xff0c;而不是通过实例化类来创建对象。这个模式允许你创建对象时避免复杂的初始化步骤&#xff0c;并且能够动态地创建对象的副本。 原型模式的关键…

Python导入Shapefile到PostGIS的常见问题和解决方案

导入Shapefile到PostGIS的常见问题和解决方案 先决条件&#xff1a; 已经拥有含有GDAL的python环境&#xff08;如果大家需要&#xff0c;我可以后面出一片文章 问题一&#xff1a;QGIS连接到PostGIS数据库失败 错误描述&#xff1a; Connection to server at &quo…

Vue3实战笔记(40)—组件逻辑复用:自定义Hooks的完全指南

文章目录 前言一、状态管理二、副作用处理三、 生命周期钩子总结 前言 自定义Hooks是Vue3中的一个重要特性&#xff0c;它允许您创建可重用的函数&#xff0c;以便在组件之间共享状态和逻辑。以下是一些关于自定义Hooks的常见用法。 一、状态管理 使用reactive或ref来创建响应…

题解:CF1969B(Shifts and Sorting)

题解&#xff1a;CF1969B&#xff08;Shifts and Sorting&#xff09; 一、题目翻译 给定一个二进制字符串&#xff0c;你可以将其中一个子段&#xff08;注意不是子串&#xff0c;是连续的&#xff09;进行循环移位——将最后一个字符放在第一个字符之前&#xff0c;其代价为…