基于Reactor模式的高性能网络库之缓冲区Buffer组件

简介: Buffer 类用于处理 Socket I/O 缓存,负责数据读取、写入及内存管理。通过预分配空间和索引优化,减少内存拷贝与系统调用,提高网络通信效率,适用于 Reactor 模型中的异步非阻塞 IO 处理。

Buffer 类用于处理 socket 的 I/O 缓存 —— 负责:

  • 从 socket 读取数据(read)
  • 写入 socket 发送数据(write)
  • 管理数据区的结构与索引,以避免频繁内存拷贝

成员变量

static   const  size_t   KCheapPrepend=8;//作用:预留头部空间(prependable space通常用于在 buffer 前面“追加”一些控制信息(比如长度头部等协议格式)
static   const    size_t   KInitialSize=1024;//Buffer 初始化时的默认大小,即初始 buffer 容量
 std::vector<char>buffer_;//真正存放数据的原始空间,大小可能会增长
 size_t   readerIndex_;//可读区域的起始位置(也就是数据有效负载的起点)初始值为 KCheapPrepend = 8,跳过头部预留空间
 size_t   writeIndex_;可写区域的起始位置(新的数据 append 时写入的位置)初始值也是 8(没有数据时读写索引相同)

数据区域结构大概可以划分为三段:  

设置缓冲区(Buffer)是网络编程中必不可少的一部分,它的主要目的是  

1. 一、解决“数据不一次收完/发完”的问题  

2.提高性能,减少系统调用,系统调用(如 read()/write())是“用户态”到“内核态”的切换,有成本

3.实现高效的异步/非阻塞 IO,在 Reactor 模型中,socket 都是非阻塞的:你不能保证什么时候 socket 可以写

所以必须把“待写数据”先存在缓冲区中,一旦可以写,就从 buffer 中拷贝一部分写出去

explicit  Buffer(size_t  initialSize=KInitialSize)://指定初始容量,默认是 KInitialSize = 1024 字节
        buffer_(KCheapPrepend+initialSize),//分配一个 std::vector<char>,大小是 initialSize + KCheapPrepend
        readerIndex_(KCheapPrepend),读指针从第 8 字节开始(跳过 prepend 区域)
        writeIndex_(KCheapPrepend)写指针从第 8 字节开始(跳过 prepend 区域)
        {
        }
        size_t   readableBytes()const//可读的数据
        {
            return   writeIndex_-readerIndex_;
        }
        
        size_t   writeableBytes()const//可写数据
        {
            return   buffer_.size()-writeIndex_;
        }
        
        size_t    prependableBytes()const{
            return  readerIndex_;
        }//0 ~ readerIndex_ 之间的区域,就是已经读过的数据,但我们没有释放掉;
        //这个区域可以被拿来做 prepend 用,因此被称为“prependable” 区。
        //返回缓冲区中可读数据的起始地址
        const  char*peek()const{
            return  begin()+readerIndex_;
        }
        void  retrieve(size_t    len)//是 Buffer 缓冲区的一个数据消费接口,用于在数据读取后推进读指针 readerIndex_,表示“已经读取或发送”了这部分数据。
        {
                if(len<readableBytes())//   把 readerIndex_ 移 len 字节,表示消费(发送)了这部分数据
                {
                        readerIndex_+=len;//应用只读取了可读缓冲区数据的一部分len,还剩下   readerIndex_+=len         -》    writeindex_
                }
                else//len>=readableBytes
                {
                    retrieveAll();//    表示读完(发送完)所有数据,直接调用 retrieveAll() 来重置索引
                }
        }
        void  retrieveAll()//   ,将读写指针都复位到 KCheapPrepend,清空状态(类似重新开始)
        {
                readerIndex_=writeIndex_=KCheapPrepend;
        }
       
        std::string    retrieveAllAsString()//将所有可读数据(readableBytes())作为一个 std::string 返回。
        {
                return   retrieveAsString(readableBytes());
        }
        std::string   retrieveAsString(size_t  len)
        {
                std::string  result(peek(),len);//从 buffer 中拷贝 len 字节数据构造字符串,len字节的数据就是要读取的数据
                retrieve(len);//上面一句把缓冲区所有可读数据,已经读取出来,现在这一句要对缓冲区进行复位操作(表示读完所有数据,直接调用 retrieveAll() 来重置索引)
                return   result;//返回构造好的字符串
        }
        
        void  ensureWriteableBytes(size_t  len)//:确保当前 buffer 至少有 len 字节的可写空间(writeable space)
        {
                if(writeableBytes()<len)
                {
                    //扩容
                    makeSpace(len);
                }
             
        }


          void   append(const char*data,size_t len)//将一段长度为 len 的内存数据(由 data 指向)追加写入到当前 buffer 的可写区域末尾。
        {
            ensureWriteableBytes(len);// 确保 buffer 当前有足够的空间可写,如果没有,就自动调用 makeSpace() 来扩容或搬移已有数据。
            std::copy(data,data+len,beginwrite());//使用 std::copy() 将传入的 data 数据拷贝 len 字节到 buffer 的写指针位置。
            //把 [first, last) 范围内的元素 复制到从 result 开始的目标区域中。
            writeIndex_+=len;//数据写入成功后,需要更新写指针,表示新的数据已经被写进 buffer,可读区域向后扩大
        }


       char   *begin()//为其他函数(如 peek(), beginWrite(), append() 等)提供统一的底层数据访问入口。
        {
            return &*buffer_.begin();// 指向 buffer_ 开头的指针    用于内部偏移计算、写入数据等
        }


        
        const char*  begin()const
        {
          return &*buffer_.begin();//只读版本   用于不修改内容的场景,例如读缓冲
        }
        
      



void   makeSpace(size_t  len)//确保 有足够空间写入 len 字节的数据, Buffer 缓冲区扩容 或 空间复用的核心逻辑
        {
            if(writeableBytes()+prependableBytes()<len+KCheapPrepend)//当前 buffer 空闲空间(可写空间 + 可移动头部空间)是否足以容纳 len 字节的数据 + 预留头部空间(KCheapPrepend)?
            {
                buffer_.resize(writeIndex_+len);//resize() 会让 buffer 的容量扩展到:当前写入位置 + 要写入的字节数
            }
            else//:空间够,但前部空闲太多,没利用起来 → 复用它!
            {
                 size_t readable=readableBytes();
                    std::copy(begin()+readerIndex_,begin()+writeIndex_,begin()+KCheapPrepend);//将未读数据 [readerIndex_, writeIndex_) 拷贝到 KCheapPrepend 后的位置(即重新排布数据),:把前面读掉的空间让出来,避免浪费内存
                    readerIndex_=KCheapPrepend;
                    writeIndex_=readerIndex_+readable;//复用前部已读空间
            }
        }
     





//在 Buffer 中,数据是否“存在”不重要,只要我们保证索引指向的是有效区域,前面未清除的内容不会影响逻辑或造成污染,所以不清除是安全且高效的。
        char*beginwrite()
        {
            return  begin()+writeIndex_;
        }
        
         const   char*beginwrite()const
        {
            return  begin()+writeIndex_;
        }// 第一个 const:修饰返回值
       //第二个 const:修饰成员函数,承诺不会修改当前对象的任何成员变量
//   /**
        




* struct iovec {
    void*  iov_base; // 可以接受任何类型的指针
    size_t iov_len;

             
             
           
    
    
    
    //高效从 socket 描述符读取数据到缓冲区的经典做法
         ssize_t   Buffer::readFD(int fd,int*saveErrno)//saveErrno 是用来 保存系统调用 readv() 失败时 errno 的值 的一个输出参数指针(int* 类型)。这是一种常见的错误信息传递方式,避免在函数内部直接处理错误,而是把错误代码返回给调用者去处理
   {
        char  extrabuf[65536]={0};//栈上的内存空间,为防止主缓冲区空间不够,额外准备一个大块栈空间暂存多余数据,避免动态扩容开销。
        struct  iovec  vec[2];        //vec[0] → buffer_ 中尚未使用的区域   vec[1] → 栈上的 extrabuf
        const  size_t  writeable=writeableBytes();          //buffer底层缓冲区剩余的可写空间大小
        vec[0].iov_base=begin()+writeIndex_;   //char* + size_t 结果是:指向 buffer 中 可写位置的 char* 指针。
        vec[0].iov_len=writeable;//vec[0] 指向主缓冲区可写位置
        vec[1].iov_base=extrabuf;
        vec[1].iov_len=sizeof  extrabuf;
        const  int iovcnt=(writeable<sizeof  extrabuf)?2:1;//若主缓冲区可写空间不足 64KB,就启用备用缓冲区
        const  ssize_t   n=::readv(fd,vec,iovcnt);//用 readv() 一次性读取数据,自动写入 vec 中定义的缓冲区,如果数据很小,会直接写到 vec[0],如果数据超出主缓冲区,就溢出到 vec[1] 中
        if(n<0)
        {
            *saveErrno=errno;
        }
        else  if(n<=writeable)//如果数据完全写入了主缓冲区,则更新写入索引。
        {
            writeIndex_+=n;
        }
        /**
         * 如果有多余数据写入了备用缓冲区,则先将主缓冲区视为满;
          再将备用缓冲区的数据追加进主缓冲区,保持数据连续。
         */
        else   //extrabuf里面也写入了数据
        {
            writeIndex_=buffer_.size();
            append(extrabuf,n-writeable);//writeIndex_开始写n-writeable大小的数据,append() 内部会自动扩容或整理空间
        }
        return  n;
   }
    
             
             ssize_t     Buffer::writeFD(int fd,int*saveErrno)//将 Buffer 中已有的数据写入某个 socket 文件描述符(fd)中
    {
        ssize_t  n=::write(fd,peek(),readableBytes());//系统调用,向 fd 写数据,peek(): 返回 Buffer 当前可读数据的起始地址(即 begin() + readerIndex_)。readableBytes(): 返回 Buffer 中当前可读的数据长度
        if(n<0)
        {
            *saveErrno=errno;
        }
        return n;//返回写入的字节数(>0)
    }
相关文章
|
5月前
|
网络协议 算法 Java
基于Reactor模型的高性能网络库之Tcpserver组件-上层调度器
TcpServer 是一个用于管理 TCP 连接的类,包含成员变量如事件循环(EventLoop)、连接池(ConnectionMap)和回调函数等。其主要功能包括监听新连接、设置线程池、启动服务器及处理连接事件。通过 Acceptor 接收新连接,并使用轮询算法将连接分配给子事件循环(subloop)进行读写操作。调用链从 start() 开始,经由线程池启动和 Acceptor 监听,最终由 TcpConnection 管理具体连接的事件处理。
217 2
|
5月前
基于Reactor模式的高性能网络库github地址
https://github.com/zyi30/reactor-net.git
142 0
|
3月前
|
监控 前端开发 安全
Netty 高性能网络编程框架技术详解与实践指南
本文档全面介绍 Netty 高性能网络编程框架的核心概念、架构设计和实践应用。作为 Java 领域最优秀的 NIO 框架之一,Netty 提供了异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。本文将深入探讨其 Reactor 模型、ChannelPipeline、编解码器、内存管理等核心机制,帮助开发者构建高性能的网络应用系统。
241 0
|
4月前
|
机器学习/深度学习 算法 数据库
基于GoogleNet深度学习网络和GEI步态能量提取的步态识别算法matlab仿真,数据库采用CASIA库
本项目基于GoogleNet深度学习网络与GEI步态能量图提取技术,实现高精度步态识别。采用CASI库训练模型,结合Inception模块多尺度特征提取与GEI图像能量整合,提升识别稳定性与准确率,适用于智能安防、身份验证等领域。
|
4月前
|
运维 监控 安全
计算机网络及其安全组件纲要
本文主要介绍了 “计算机网络及常见组件” 的基本概念,涵盖网卡、IP、MAC、OSI模型、路由器、交换机、防火墙、WAF、IDS、IPS、域名、HTTP、HTTPS、网络拓扑等内容。
285 0
|
12月前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
279 17
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
231 10
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,并提供一些实用的代码示例。通过阅读本文,您将了解到如何保护自己的网络安全,以及如何提高自己的信息安全意识。
244 10
|
监控 安全 网络安全
网络安全与信息安全:漏洞、加密与意识的交织
在数字时代的浪潮中,网络安全与信息安全成为维护数据完整性、保密性和可用性的关键。本文深入探讨了网络安全中的漏洞概念、加密技术的应用以及提升安全意识的重要性。通过实际案例分析,揭示了网络攻击的常见模式和防御策略,强调了教育和技术并重的安全理念。旨在为读者提供一套全面的网络安全知识框架,从而在日益复杂的网络环境中保护个人和组织的资产安全。

热门文章

最新文章