muduo缓冲区Buffer类

mainLoop给subLoop传递的TcpConnection对象包含以下一些主要的成员,我们来看一下这两个Buffer数据成员
在这里插入图片描述
我们在TCP通信过程中经常会发生TCP的粘包问题,我们一般会在数据头部分加上数据的长度,通信双方可以根据数据的长度,读取信息,从而避免粘包问题

接收端可能接收的数据比较多,而应用程序目前只需要一部分数据,那剩下的数据就得存放在缓冲区

同理,发送端可能需要发送的数据比较多,一次无法发送完成,剩下的数据也需要存放在缓冲区

Buffer类具有以下数据成员
在这里插入图片描述
在这里插入图片描述

  • prependable bytes:表示数据包的字节数
  • readerIndex:应用程序从readerIndex指向的位置开始读缓冲区,[readerIndex, writerIndex]表示待读取数据,读完后向后移动len(retrieve方法)
  • writerIndex:应用程序从writerIndex指向的位置开始写缓冲区,写完后writerIndex向后移动len(append方法)
  • [readerIndex_, writerIndex_]:标识可读数据区间(readableBytes方法)

1. retrieve相关方法

retrieve就是从Buffer读取数据

void retrieve(size_t len){
    // len就是应用程序从Buffer缓冲区读取的数据长度
    // 必须要保证len <= readableBytes()
    if(len < readableBytes()){
        // 这里就是可读数据没有读完
        readerIndex_ += len;
    }else{
        // len == readableBytes()
        // 可读数据读完了,readerIndex_和writerIndex_都要复位
        retrieveAll();
    }
}

void retrieveAll(){
    readerIndex_ = kCheapPrepend;
    writerIndex_ = kCheapPrepend;
}

std::string retrieveAsString(size_t len){
    if(len <= readableBytes()){
        std::string result(peek(), len);   // peek返回缓冲区中可读数据的起始地址,从可读地址开始截取len个字符
        retrieve(len);                     // result保存了缓冲区中的可读数据,retrieve将参数复位
        return result;
    }
}

// onMessage 有数据到来时,把数据打包成Buffer对象,retrieveAllAsString把Buffer类型转成string
std::string retrieveAllAsString(){
    return retrieveAsString(readableBytes());
}

2. 扩容函数Buffer::makeSpace

如果需要写入缓冲区数据的长度要大于Buffer对象底层vector空闲的长度了,就需要扩容,其中len表示需要写入数据的长度

void makeSpace(size_t len){
    // writableBytes() + prependableBytes() < len + kCheapPrepend
    if(buffer_.size() - (writerIndex_ - readerIndex_) < len + kCheapPrepend){
    	// 直接在writerIndex_后面再扩大len的空间
        buffer_.resize(writerIndex_ + len);
    }else{
    	// 如果是空闲空间足够存放len字节的数据,就把未读取的数据统一往前移,移到kCheapPrepend的位置
        size_t readable = readableBytes();  // 这表示剩余需要读取的数据
        // 把[readerIndex_, writerIndex_]整体搬到kCheapPrepend
        std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + kCheapPrepend);
        readerIndex_ = kCheapPrepend;
        writerIndex_ = readerIndex_ + readable;  // writerIndex_指向待读取数据的末尾
    }
}
if(buffer_.size() - (writerIndex_ - readerIndex_) < len + kCheapPrepend)

在这里插入图片描述
该判断语句中(writerIndex_ - readerIndex_)表示未读取数据的区间,用buffer_长度一减,则表示剩余空闲区间

整个if条件就是,剩余空闲区间不够存储将要写入缓冲区的len数据了,就需要makeSpace扩容,直接在writerIndex_后面再扩大len的空间

如果剩余空闲区间够存储将要写入缓冲区的len数据,就把待读取数据[readerIndex_, writerIndex_]整体搬到kCheapPrepend

3. appned方法

不管是从fd上读数据写到缓冲区inputBuffer_,还是发数据要写入outputBuffer_,我们都要往writeable区间内添加数据

void append(const char* data, size_t len){
    // 确保可写空间不小于len
    ensureWritableBytes(len);
    // 把data中的数据往可写的位置writerIndex_写入
    std::copy(data, data+len, beginWrite());
    // len字节的data写入到Buffer后,就移动writerIndex_
    writerIndex_ += len;
}

4. Buffer::readFd读取数据

从fd上读取数据,存放到writerIndex_,返回实际读取的数据大小

ssize_t readFd(int fd, int* saveErrno);

Buffer缓冲区是有大小的(占用堆区内存),但是我们无法知道fd上的流式数据有多少,如果我们将缓冲区开的非常大,大到肯定能容纳所有读取的数据,这就太浪费空间了,muduo库中使用readv方法,根据读取的数据多少开动态开辟缓冲区
在这里插入图片描述

readv可以在不连续的多个地址写入同一个fd上读取的数据

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
  • fd:读取数据的文件描述符
  • iov:封装了缓冲区地址和可写空间大小的结构体
  • iovcnt:缓冲区个数
struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};
  • iov_base:缓冲区的起始地址
  • iov_len:缓冲区的可允许写的长度

Buffer.cc

#include "Buffer.h"

#include <sys/uio.h>
#include <errno.h>
#include <unistd.h>

ssize_t Buffer::readFd(int fd, int* saveErrno){
    char extrabuff[65536] = {0};  // 64K栈空间,会随着函数栈帧回退,内存自动回收
    struct iovec vec[2];

    const size_t writable = writableBytes();   // Buffer底层剩余的可写空间

    vec[0].iov_base = begin() + writerIndex_;  // 第一块缓冲区
    vec[0].iov_len = writable;                 // iov_base缓冲区可写的大小

    vec[1].iov_base = extrabuff;               // 第二块缓冲区
    vec[1].iov_len = sizeof(extrabuff);
	
	// 如果Buffer有65536字节的空闲空间,就不使用栈上的缓冲区,如果不够65536字节,就使用栈上的缓冲区,即readv一次最多读取65536字节数据
    const int iovcnt = (writable < sizeof(extrabuff)) ? 2 : 1;  
    const ssize_t n = ::readv(fd, vec, iovcnt);

    if(n < 0){
        *saveErrno = errno;
    }else if(n <= writable){
        // 读取的数据n小于Buffer底层的可写空间,readv会直接把数据存放在begin() + writerIndex_
        writerIndex_ += n;
    }else{
        // Buffer底层的可写空间不够存放n字节数据,extrabuff有部分数据(n - writable)
        // 从缓冲区末尾再开始写
        writerIndex_ = buffer_.size();
        // 从extrabuff里读取 n - writable 字节的数据存入Buffer底层的缓冲区
        append(extrabuff, n - writable);
    }

    return n;
}

ssize_t Buffer::writeFd(int fd, int* saveErrno){
    ssize_t n = ::write(fd, peek(), readableBytes());
    if (n < 0){
        *saveErrno = errno;
    }
    return n;
}

使用了65536字节的栈空间,开辟效率高,回收快,用于存储Buffer对象无法存储的,剩余的数据

我们给readv两个缓冲区,第一个就是Buffer对象的空间(一般是堆空间),第二个是65536字节的栈空间。readv会先写入第一个缓冲区,没写完再写入第二个缓冲区。如果读取了65536字节数据,fd上的数据还是没有读完,那就等Poller下一次上报(工作在LT模式),继续读取,数据不会丢失

5. Buffer.h

#pragma once

#include <vector>
#include <string>
#include <algorithm>

class Buffer{
    public:
        static const size_t kCheapPrepend = 8;   // 数据包长度用8字节存储
        static const size_t kInitailSize = 1024; // 缓冲区初始大小

        explicit Buffer(size_t initialSize = kInitailSize)
            : buffer_(kCheapPrepend + initialSize)              // 底层vector的长度
            , readerIndex_(kCheapPrepend)
            , writerIndex_(kCheapPrepend)
        {}

        // 待读取数据长度:[readerIndex_, writerIndex_]
        const size_t readableBytes() const{
            return writerIndex_ - readerIndex_;                 
        }

        // 可写空闲大小
        const size_t writableBytes() const{
            return buffer_.size() - writerIndex_;               
        }

        const size_t prependableBytes() const{
            return readerIndex_;
        }

        // 返回缓冲区中可读数据的起始地址
        const char* peek() const{
            return begin() + readerIndex_;
        }

        void retrieve(size_t len){
            // len就是应用程序从Buffer缓冲区读取的数据长度
            // 必须要保证len <= readableBytes()
            if(len < readableBytes()){
                // 这里就是可读数据没有读完
                readerIndex_ += len;
            }else{
                // len == readableBytes()
                // 可读数据读完了,readerIndex_和writerIndex_都要复位
                retrieveAll();
            }
        }

        void retrieveAll(){
            readerIndex_ = kCheapPrepend;
            writerIndex_ = kCheapPrepend;
        }

        std::string retrieveAsString(size_t len){
            if(len <= readableBytes()){
                std::string result(peek(), len);   // peek返回缓冲区中可读数据的起始地址,从可读地址开始截取len个字符
                retrieve(len);                     // result保存了缓冲区中的可读数据,retrieve将参数复位
                return result;
            }
        }

        // onMessage 有数据到来时,把数据打包成Buffer对象,retrieveAllAsString把Buffer类型转成string
        std::string retrieveAllAsString(){
            return retrieveAsString(readableBytes());
        }

        // 可写缓冲区为[writerIndex, buffer_.size()],需要写入的数据长度为len
        void ensureWritableBytes(size_t len){
            if(writableBytes() < len){
                makeSpace(len);     // 扩容
            }
        }

        void append(const char* data, size_t len){
            // 确保可写空间不小于len
            ensureWritableBytes(len);
            // 把data中的数据往可写的位置writerIndex_写入
            std::copy(data, data+len, beginWrite());
            // len字节的data写入到Buffer后,就移动writerIndex_
            writerIndex_ += len;
        }
        
        char* beginWrite() {
            return begin() + writerIndex_;
        }

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

        // 从fd上读取数据,存放到writerIndex_,返回实际读取的数据大小
        ssize_t readFd(int fd, int* saveErrno);

        
    private:
        // 返回Buffer底层数据首地址
        char* begin(){
            return &(*buffer_.begin());
        }

        // 常对象只能调用常方法,不能调用普通方法
        const char* begin() const {
            return &(*buffer_.begin());
        }

        void makeSpace(size_t len){
            // writableBytes() + prependableBytes() < len + kCheapPrepend
            if(buffer_.size() - (writerIndex_ - readerIndex_) < len + kCheapPrepend){
                buffer_.resize(writerIndex_ + len);
            }else{
                size_t readable = readableBytes();
                std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + kCheapPrepend);
                readerIndex_ = kCheapPrepend;
                writerIndex_ = readerIndex_ + readable;
            }
        }

        std::vector<char> buffer_;                               // vector管理的资源自动释放,Buffer对象在哪个区,buffer_就在哪个区,主要利用vector自动扩容的功能
        size_t readerIndex_;
        size_t writerIndex_;
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值