C++仿mudo库高并发服务器项目:Buffer模块

前言

本篇文章所讲的是本人的个人项目仿muduo库高并发服务器中的Buffer模块实现部分。

Buffer模块介绍:

Buffer类⽤于实现⽤⼾态缓冲区,提供数据缓冲,取出等功能。

  • 功能:用于实现通信套接字的用户态缓冲区

  • 意义:

    1. 防止接收到的数据不是一条完整的数据(提示:TCP面向字节流),因此对接收的数据进行缓冲
    2. 对客户端响应的数据,应该是在套接字可写的情况下进行发送
  • 功能设计:

    1. 向缓冲区中添加数据
    2. 从缓冲区中取出数据

实现思路:

  1. 实现缓冲区得有一块内存空间,采用vector,vector底层其实使用的就是一个线性的内存空间。

    这里不使用string作为容器是因为,缓冲器要存储的数据很大可能会包含字符0等字符,而string的方法往往会将它识别为数据结尾

  2. 要素:

    1. 默认的空间大小。

    2. 当前的读取数据位置。

    3. 当前的写入数据位置。

记录当前读取位置和当前写入位置是因为,在数据被读走时我们不需要移动数据,只需要更新下一次读取位置即可,插入时同理。

写入操作:

当前写入位置在哪,就在哪写,当待写入空间大小,无法满足接下来的写入需求,这时我们就面临两种选择:

  1. 待写入数据所需空间大小<待写入空间+可覆盖空间,这种情况下我们只需要移动数据到容器开始处,再写入待写入数据即可。
  2. 待写入数据所需空间大小>待写入空间+可覆盖空间,这种情况下我们只能选择对缓存空间扩容。

读取操作:

当前读取位指向哪里,就从哪里开始读取,不过前提是得有数据可读;而可读数据的大小,是用当前写入位置减去当前读取位置来计算的。

代码实现:

c++ 复制代码
const uint64_t BUFFER_DEFAULT_SIZE = 1024; 
class Buffer
{
private:
    std::vector<char> _buffer;//使用vector进行内存空间管理
    uint64_t _reader_idx;//读偏移
    uint64_t _write_idx;//写偏移

public:
    Buffer()
        :_reader_idx(0)
        ,_write_idx(0)
        ,_buffer(BUFFER_DEFAULT_SIZE)
    {}

    char* Begin(){ return &*_buffer.begin(); }
    //获取当前写入起始地址
    char* WritePosition()
    {
        return Begin() + _write_idx;
    }
    //获取当前读取起始地址
    char* ReadPosition()
    {
        return Begin() + _reader_idx;
    }
    //获取缓冲区末尾空闲空间大小
    uint64_t TailIdleSize()
    {
        return _buffer.size() - _write_idx;
    }
    //获取缓冲区起始空闲空间大小
    uint64_t HeadIdleSize()
    {
        return _reader_idx;
    }
    //获取可读数据大小
    uint64_t ReadAbleSize()
    {
        return _write_idx - _reader_idx;
    }
    //将读偏移向后移动
    void MoveReadOffset(uint64_t len)
    {
        if(len == 0) return;
        //必须小于可读空间大小
        assert(ReadAbleSize() >= len);
        _reader_idx += len;
    }
    //将写偏移向后移动
    void MoveWriteOffset(uint64_t len)
    {
        //必须小于尾部空间大小
        assert(TailIdleSize() >= len);
        _write_idx += len;
    }
    //确保可写空间足够len
    void EnsureWriteSpace(uint64_t len)
    {
        //尾部空间够,直接返回
        if(TailIdleSize() >= len) return;
        //总体空间够,将数据平移到最开始,为后面腾位置
        if(TailIdleSize() + HeadIdleSize() >= len)
        {
            //保存一下可读数据大小
            uint64_t rsz = ReadAbleSize();
            std::copy(ReadPosition(),ReadPosition() + rsz,Begin());//可读数据拷贝到起始位置
            _reader_idx = 0;
            _write_idx = rsz;
        }
        //不够,扩容
        else
        {
            _buffer.resize(_write_idx + len);
        }
    }
    //写入数据
    void Write(const void* date,uint64_t len)
    {
        //保证空间足够
        if(len == 0) return;
        EnsureWriteSpace(len);
        //将数据拷贝进去
        const char* d = (const char*)date;
        std::copy(d,d + len,WritePosition());
    }
    //写入数据并且更新写指针
    void WriteAndPush(const void* date,uint64_t len)
    {
        Write(date,len);
        MoveWriteOffset(len);
    }
    //写入一个字符串
    void WriteString(const std::string& date)
    {
        Write(date.c_str(),date.size());
    }
    //写入一个字符串并且更新写指针
    void WriteStringAndPush(const std::string& date)
    {
        WriteString(date);
        MoveWriteOffset(date.size());
    }
    //写入一个Buffer
    void WriteBuffer(Buffer& date)
    {
        Write(date.ReadPosition(),date.ReadAbleSize());
    }
    //写入一个Buffer并且更新写指针
    void WriteBufferAndPush(Buffer& date)
    {
        Write(date.ReadPosition(),date.ReadAbleSize());
        MoveWriteOffset(date.ReadAbleSize());
    }
    //读取数据
    void Read(void* buf,uint64_t len)
    {
        //获取数据的长度必须小于可读数据
        assert(len <= ReadAbleSize());
        std::copy(ReadPosition(),ReadPosition() + len,(char*)buf);
    }
    //读取数据并且更新读指针
    void ReadAndPop(void* buf,uint64_t len)
    {
        Read(buf,len);
        MoveReadOffset(len);
    }
    //读取一个长度为len的字符串
    std::string ReadString(uint64_t len)
    {
        assert(len <= ReadAbleSize());
        std::string str;
        str.resize(len);
        Read(&str[0],len);
        return str;
    }
    //读取一个长度为len的字符串并且更新读指针
    std::string ReadStringAndPop(uint64_t len)
    {
        assert(len <= ReadAbleSize());
        std::string str = ReadString(len);
        MoveReadOffset(len);
        return str;
    }

    //查找换行符
    char* FindCRLF()
    {
        char* res = (char*)memchr(ReadPosition(),'\n',ReadAbleSize());
        return res;
    }

    //获取一行字符串
    std::string GetLine()
    {
        //找到换行符位置
        char* pos = FindCRLF();
        if(pos == nullptr) return "";
        //+1是为了把换行符也取出来
        return ReadString(pos - ReadPosition() + 1);
    }
    //获取一行字符串并且更新读指针
    std::string GetLineAndPop()
    {
        std::string str = GetLine();
        MoveReadOffset(str.size());
        return str;
    }
    //清空缓冲区
    void Clear()
    {
        //偏移量归零
        _reader_idx = _write_idx = 0;
    }
};

测试代码:

c++ 复制代码
#include "Buffer.hpp"

int main()
{

    Buffer buf;
    for(int i=0;i<300;i++)
    {
       std::string  ret = "hello linux "+std::to_string(i)+'\n';
       buf.WriteStringAndPush(ret);
    }
    while(buf.ReadAbleSize()>0)
    {
        std::cout << buf.GetLineAndPop();
    }

    std::cout << buf.ReadAbleSize() << std::endl;
    std::string str1;
    str1 = buf.ReadStringAndPop(buf.ReadAbleSize());
    std::cout << str1 << std::endl;

    Buffer buf0;
    std::string str2="hello linux !!";
    buf0.WriteStringAndPush(str2);
    std::cout << buf0.ReadAbleSize() << std::endl;

    Buffer buf1;
    buf1.WriteBufferAndPush(buf0);
    std::cout << buf1.ReadAbleSize() << std::endl;

    return 0;
}

测试结果:

相关推荐
刘岩Tony5 小时前
ssh别名和多服务器同步文件
运维·服务器·ssh
zzy20887402715 小时前
自定义服务器实现时间同步
运维·服务器
LXY_BUAA5 小时前
在电脑中安装双系统(win11 + linux)20251019
linux·运维·服务器
江公望5 小时前
Qt qmlplugindump浅谈
开发语言·qt·qml
曦樂~5 小时前
【Qt】文件操作/事件--mainwindow做编辑器
开发语言·qt
敲代码的瓦龙5 小时前
西邮移动应用开发实验室2025年二面题解
开发语言·c++·算法
laocooon5238578865 小时前
一个适合新手的训练C题
c语言·开发语言
C嘎嘎嵌入式开发6 小时前
(21)100天python从入门到拿捏《XML 数据解析》
xml·开发语言·python
晚风残6 小时前
【C++ Primer】第十七章:标准库特殊设施
开发语言·c++