Muduo库源码剖析(七)——缓冲区Buffer类

Buffer类

要点

由于Muduo库使用的是非阻塞IO模型,即每次send() 不一定全发完,没发完的数据要用一个容器进行接收,所以必须要实现应用层缓冲区.

缓冲区中各个指针和区域,下图要牢牢记住:

Muduo库源码剖析(七)——缓冲区Buffer类_第1张图片

其中prependable bytes 通常用于解决粘包,kCheapPrepend是为了在序列化的时候可以便宜的在首部增加几个字节而不必腾挪 std::vector 的内部空间。

缓冲区底层使用 std::vector利用其动态扩容的机制。

注意读写缓冲区 都有readerIndex 和 writerIndex!!!

而不是读缓冲区没有writerIndex,写缓冲区没有readerIndex

注:利用 readv 分散读,利用 writev 可聚集写

重点代码详解

// Buffer.h
#pragma once

#include 
#include 
#include 

class Buffer
{
public:
    static const size_t kCheapPrepend = 8; //
    static const size_t kInitialSize = 1024;

    explicit Buffer(size_t initialSize = kInitialSize)
        : buffer_(kCheapPrepend + initialSize)
        , readerIndex_(kCheapPrepend)
        , writerIndex_(kCheapPrepend)
    {}

    size_t readableBytes() const
    {
        return writerIndex_ - readerIndex_;
    }

    size_t writeableBytes() const
    {
        return buffer_.size() - writerIndex_;
    }

    size_t prependableBytes() const
    {
        return readerIndex_;
    }

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

    void retrieve(size_t len)
    {
        if(len < readableBytes())
        {
            // 应用只读取了可读缓冲区数据的部分,即len长度
            // 还剩[readerIndex_ + len, writerIndex_]区间长度数据没读
            readerIndex_ += len;
        }
        else 
        {
            retriveAll();
        }
    }

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

    // 把onMessage上报的Buffer数据,转成string类型返回
    // 取出所有 readable 的数据转换为string返回  
    std::string retriveAllAsString(size_t len)
    {
        return retriveAsString(readableBytes());
    }

    std::string retriveAsString(size_t len)
    {
        std::string result(peek(), len);
        retrieve(len); // 读完缓冲区各个指针要置位
        return result;
    }

    // 确保有长度为len的写缓冲区可用
    void ensureWriteableBytes(size_t len)
    {
        if(writeableBytes() < len)
        {
            makeSpace(len); // 扩容
        }
    }

    // 把[data, data+len]内存上的数据,添加到readable缓冲区中
    void append(const char *data, size_t len)
    {
        ensureWriteableBytes(len); 
        std::copy(data, data + len, beginWrite());
        writerIndex_ += len; // 移动缓冲区可写的起始位置
    }

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

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

    // 从fd上读数据
    ssize_t readFd(int fd, int *saveErrno);

    // 通过fd发送数据
    ssize_t writeFd(int fd, int *saveErrno);

private:
    char* begin()
    {
        return &*buffer_.begin(); // vector底层数组首元素的地址,也可以用buffer_.data()
    }
    const char* begin() const
    {
        return &*buffer_.begin();
    }

    // 扩充写缓冲区空间
    void makeSpace(size_t len)
    {
        // 根据buffer结构图理解即可
        // writeableBytes() + prependableBytes() - kCheapPrepend < len 
        // 可写空间不够
        if(writeableBytes() + prependableBytes() < 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_; // 
    // 注意:读写缓冲区都有可读和可写起始位置!!!
    size_t readerIndex_; // 可读数据起始位置
    size_t writerIndex_; // 可写入起始位置
};

// Buffer.cc
#include "Buffer.h"

#include 
#include 
#include 

// 从fd上读数据, Poller工作在LT模式
// Buffer缓冲区有大小! 但从fd读数据时,不知道tcp数据最终大小
ssize_t Buffer::readFd(int fd, int *saveErrno)
{
    char extrabuf[65536] = {0}; // 栈上内存空间 64k

    struct iovec vec[2];
    const size_t writeable = writeableBytes(); // 这是Buffer底层缓冲区剩余可写空间大小
    vec[0].iov_base = begin() + writerIndex_;
    vec[0].iov_len = writeable;

    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof extrabuf;

    // writable < sizeof extrabuf 表示底层可写的缓冲区空间不够大,用两块
    const int iovcnt = (writeable < sizeof extrabuf) ? 2 : 1;
    const ssize_t n = ::readv(fd, vec, iovcnt); // scatter input,分散读
    if(n < 0)
    {
        *saveErrno = errno;
    }
    else if(n <= writeable) // Buffer的可写缓冲区够存储读出来的数据
    {
        writerIndex_ += n; // 读数据当然writerIndex_后移,结合图
    }
    else // extrabuf里面也写入了数据
    {
        writerIndex_ = buffer_.size();
        append(extrabuf, n - writeable);
    }
    return n;
}

// 通过fd发送数据
ssize_t Buffer::writeFd(int fd, int *saveErrno)
{
    // 可读的数据通过fd发出去
    ssize_t n = ::write(fd, peek(), readableBytes());
    if(n < 0)
    {
        *saveErrno = errno;
    }
    return n;
}

你可能感兴趣的:(Muduo,网络编程,C/C++,c++,服务器,linux)