C 仿 muduo 高并发服务器用户态缓冲区 Buffer 模块实现1. 为什么需要 Buffer在非阻塞 I/O 的网络编程中read和write不保证一次就能读完或写完所有数据读半包一次read可能只收到请求的一部分需要暂存起来等下次数据到达再拼接。写半包一次write可能只发送了部分数据剩余数据需要缓冲等 socket 可写时继续发送。粘包多次发送的数据可能被合并成一个 TCP 包到达需要按协议格式拆分。因此服务器需要一个用户态缓冲区在应用层管理数据的接收和发送。muduo 中的Buffer就是这样一个组件本项目仿照它实现了一个简化版。2. 代码总览#include vector #include stdint.h #include assert.h #include iostream #include string #include cstring #include ctime #define BUFFER_DEFAULT_SIZE 1024 class Buffer { private: std::vectorchar _buffer; // 使用 vector 管理内存 uint64_t _reader_idx; // 读偏移 uint64_t _writer_idx; // 写偏移 public: Buffer() : _reader_idx(0), _writer_idx(0), _buffer(BUFFER_DEFAULT_SIZE) {} char *Begin() { return *_buffer.begin(); } char *WritePosition() { return Begin() _writer_idx; } char *ReadPosition() { return Begin() _reader_idx; } uint64_t TailIdleSize() { return _buffer.size() - _writer_idx; } uint64_t HeadIdleSize() { return _reader_idx; } uint64_t ReadAbleSize() { return _writer_idx - _reader_idx; } void MoveReadOffset(uint64_t len) { assert(len ReadAbleSize()); _reader_idx len; } void MoveWriteOffset(uint64_t len) { assert(len TailIdleSize()); _writer_idx len; } void EnsureWriteSpace(uint64_t len) { if (TailIdleSize() len) return; if (len TailIdleSize() HeadIdleSize()) { uint64_t rsz ReadAbleSize(); std::copy(ReadPosition(), ReadPosition() rsz, Begin()); _reader_idx 0; _writer_idx rsz; } else { _buffer.resize(_writer_idx len); } } void Write(const void *data, uint64_t len) { EnsureWriteSpace(len); const char *d (const char *)data; std::copy(d, d len, WritePosition()); } void WriteAndPush(const void *data, uint64_t len) { Write(data, len); MoveWriteOffset(len); } void WriteString(const std::string data) { return Write(data.c_str(), data.size()); } void WriteStringAndPush(const std::string data) { WriteString(data); MoveWriteOffset(data.size()); } void WriteBuffer(Buffer data) { return Write(data.ReadPosition(), data.ReadAbleSize()); } void WriteBufferAndPush(Buffer data) { WriteBuffer(data); MoveWriteOffset(data.ReadAbleSize()); } void Read(void *buf, uint64_t len) { assert(len ReadAbleSize()); std::copy(ReadPosition(), ReadPosition() len, (char *)buf); } void ReadAndPop(void *buf, int len) { Read(buf, len); MoveReadOffset(len); } std::string ReadAsString(uint64_t len) { assert(len ReadAbleSize()); std::string str; str.resize(len); Read(str[0], len); return str; } std::string ReadAsStringAndPop(uint64_t len) { assert(len ReadAbleSize()); std::string str ReadAsString(len); MoveReadOffset(len); return str; } char *FindCRLF() { char *res (char *)memchr(ReadPosition(), \n, ReadAbleSize()); return res; } std::string GetLine() { char *pos FindCRLF(); if (pos NULL) return ; return ReadAsString(pos - ReadPosition() 1); } std::string GetLineAndPop() { std::string str GetLine(); MoveReadOffset(str.size()); return str; } void Clear() { _reader_idx 0; _writer_idx 0; } };3. 核心设计思想Buffer 的设计围绕三个核心读写偏移分离用_reader_idx和_writer_idx标记读位置和写位置而不是每次读走数据就删除 vector 头部。这样避免频繁内存移动提高效率。空间整理与扩容当尾部空间不足时先尝试把可读数据移动到头部利用头部空闲空间如果总空间仍不足再对 vector 扩容。按行读取支持提供FindCRLF、GetLine、GetLineAndPop方便 HTTP 等文本协议按行解析。4. 成员变量与接口拆解4.1 成员变量std::vectorchar _buffer; // 底层内存 uint64_t _reader_idx; // 读偏移 uint64_t _writer_idx; // 写偏移_buffer用std::vectorchar管理内存自动扩容。_reader_idx下一个待读取数据的位置。_writer_idx下一个待写入数据的位置。可读数据区间[_reader_idx, _writer_idx)。空闲空间头部空闲[0, _reader_idx)尾部空闲[_writer_idx, _buffer.size())4.2 获取位置char *Begin() { return *_buffer.begin(); } char *WritePosition() { return Begin() _writer_idx; } char *ReadPosition() { return Begin() _reader_idx; }Begin()返回 vector 首元素地址。*_buffer.begin()是获取首地址的惯用写法。WritePosition()写起始地址。ReadPosition()读起始地址。4.3 空闲空间计算uint64_t TailIdleSize() { return _buffer.size() - _writer_idx; } uint64_t HeadIdleSize() { return _reader_idx; } uint64_t ReadAbleSize() { return _writer_idx - _reader_idx; }TailIdleSize()写偏移之后的空闲空间。HeadIdleSize()读偏移之前的空闲空间。ReadAbleSize()可读数据大小。4.4 移动偏移void MoveReadOffset(uint64_t len) { assert(len ReadAbleSize()); _reader_idx len; } void MoveWriteOffset(uint64_t len) { assert(len TailIdleSize()); _writer_idx len; }移动前用assert检查越界Debug 下可及时发现问题。读偏移后移表示数据已被消费。写偏移后移表示数据已写入。4.5 确保可写空间整理与扩容void EnsureWriteSpace(uint64_t len) { if (TailIdleSize() len) return; if (len TailIdleSize() HeadIdleSize()) { uint64_t rsz ReadAbleSize(); std::copy(ReadPosition(), ReadPosition() rsz, Begin()); _reader_idx 0; _writer_idx rsz; } else { _buffer.resize(_writer_idx len); } }这是 Buffer 最核心的函数之一尾部空间足够直接返回。尾部不够但头部尾部够把可读数据整体移动到头部。保存可读数据大小rsz。用std::copy把数据搬到Begin()。_reader_idx 0_writer_idx rsz。这样尾部就腾出了空间。总空间不够直接扩容到_writer_idx len保证能写下新数据。不移动数据因为移动也没用总空间不够。为什么先移动再扩容移动可以复用头部空闲空间减少内存分配次数提高性能。4.6 写入数据void Write(const void *data, uint64_t len) { EnsureWriteSpace(len); const char *d (const char *)data; std::copy(d, d len, WritePosition()); } void WriteAndPush(const void *data, uint64_t len) { Write(data, len); MoveWriteOffset(len); }Write只拷贝数据不移动写偏移。WriteAndPush拷贝后移动写偏移表示数据已写入。其他写入接口都是对这两个的封装WriteString/WriteStringAndPush写入std::string。WriteBuffer/WriteBufferAndPush把另一个 Buffer 的可读数据写入当前 Buffer。4.7 读取数据void Read(void *buf, uint64_t len) { assert(len ReadAbleSize()); std::copy(ReadPosition(), ReadPosition() len, (char *)buf); } void ReadAndPop(void *buf, int len) { Read(buf, len); MoveReadOffset(len); }Read只拷贝数据不移动读偏移。ReadAndPop读取后移动读偏移表示数据已消费。ReadAsString/ReadAsStringAndPop读取为std::string。注意ReadAndPop的参数是int len而其他接口是uint64_t类型不一致建议统一为uint64_t。4.8 按行读取char *FindCRLF() { char *res (char *)memchr(ReadPosition(), \n, ReadAbleSize()); return res; } std::string GetLine() { char *pos FindCRLF(); if (pos NULL) return ; return ReadAsString(pos - ReadPosition() 1); } std::string GetLlineAndPop() { // 拼写错误应为 GetLineAndPop std::string str GetLine(); MoveReadOffset(str.size()); return str; }FindCRLF()在可读数据中查找\n返回指针。GetLine()从读位置到\n包含\n读取为字符串。如果没找到换行返回空串。GetLineAndPop()读取一行并移动读偏移。为什么找\n而不是\r\nHTTP 协议中行结束可能是\r\n或\n查找\n更通用解析时再处理\r。注意函数名拼写错误GetLlineAndPop应为GetLineAndPop。4.9 清空缓冲区void Clear() { _reader_idx 0; _writer_idx 0; }只重置偏移量不释放内存下次写入直接覆盖。5. 关键流程5.1 写入流程调用Write或WriteAndPush。EnsureWriteSpace(len)确保空间足够尾部够 → 直接写。尾部不够但总空闲够 → 数据移到头部再写。总空间不够 → 扩容再写。std::copy拷贝数据到WritePosition()。如果是WriteAndPushMoveWriteOffset(len)。5.2 读取流程调用Read或ReadAndPop。assert检查可读数据足够。std::copy从ReadPosition()拷贝到目标缓冲区。如果是ReadAndPopMoveReadOffset(len)。5.3 按行解析流程FindCRLF()用memchr找\n。GetLine()计算长度并读取。GetLineAndPop()读取后移动读偏移。如果没找到\n返回空串等待更多数据。6. 使用示例Buffer buf; std::string data GET /hello HTTP/1.1\r\nHost: localhost\r\n\r\n; buf.WriteStringAndPush(data); std::string line buf.GetLineAndPop(); // GET /hello HTTP/1.1\r\n std::cout line; while (true) { std::string l buf.GetLineAndPop(); if (l.empty()) break; std::cout l; }