TCP 是位元組流協定,無訊息邊界。將結構體直接 memcpy 發送雖然簡單,但面臨記憶體對齊差異、可變長度資料不支援、跨平台端序問題三大痛點。本文實作一個生產級 ByteBuffer,支援基本型別、STL 容器、字串的序列化/反序列化,解決網路封包編解碼難題。
| 問題 | 說明 |
|---|---|
| 記憶體對齊 | 編譯器插入填充位元組,不同平台/編譯選項導致大小不一 |
| 端序差異 | x86 小端序,網路大端序,ARM 可配置,直接傳輸會解析錯誤 |
| 可變長資料 | std::string、std::vector 內含指標,memcpy 只複製指標值而非內容 |
| 版本演進 | 結構體新增欄位會破壞舊版本相容性 |
readPos / writePos 支援流式操作#if defined(_MSC_VER)
typedef signed __int8 int8;
typedef unsigned __int8 uint8;
typedef signed __int16 int16;
typedef unsigned __int16 uint16;
typedef signed __int32 int32;
typedef unsigned __int32 uint32;
typedef signed __int64 int64;
typedef unsigned __int64 uint64;
#else
#include <stdint.h>
typedef int8_t int8; typedef uint8_t uint8;
typedef int16_t int16; typedef uint16_t uint16;
typedef int32_t int32; typedef uint32_t uint32;
typedef int64_t int64; typedef uint64_t uint64;
#endif
class ByteBuffer {
public:
static const size_t DEFAULT_SIZE = 0x1000; // 4KB 初始容量
ByteBuffer() : readPos_(0), writePos_(0) {
storage_.reserve(DEFAULT_SIZE);
}
explicit ByteBuffer(size_t res) : readPos_(0), writePos_(0) {
storage_.reserve(res);
}
// 清除緩衝區
void Clear() {
storage_.clear();
readPos_ = writePos_ = 0;
}
// 寫入原始位元組
void Append(const uint8* src, size_t len) {
if (writePos_ + len > storage_.size())
storage_.resize(writePos_ + len);
memcpy(&storage_[writePos_], src, len);
writePos_ += len;
}
// 讀取原始位元組
void Read(uint8* dest, size_t len) {
if (readPos_ + len > writePos_) throw std::out_of_range("Buffer underflow");
memcpy(dest, &storage_[readPos_], len);
readPos_ += len;
}
// 通用序列化:基本型別
template <typename T>
void Write(T value) {
// 網路序轉換(大端序)
T netValue = HostToNet(value);
Append(reinterpret_cast<const uint8*>(&netValue), sizeof(T));
}
template <typename T>
T Read() {
T netValue;
Read(reinterpret_cast<uint8*>(&netValue), sizeof(T));
return NetToHost(netValue);
}
// 運算子多載鏈式呼叫
ByteBuffer& operator<<(bool v) { Write<uint8>(v); return *this; }
ByteBuffer& operator<<(uint8 v) { Write(v); return *this; }
ByteBuffer& operator<<(uint16 v) { Write(v); return *this; }
ByteBuffer& operator<<(uint32 v) { Write(v); return *this; }
ByteBuffer& operator<<(uint64 v) { Write(v); return *this; }
ByteBuffer& operator<<(int8 v) { Write(v); return *this; }
ByteBuffer& operator<<(int16 v) { Write(v); return *this; }
ByteBuffer& operator<<(int32 v) { Write(v); return *this; }
ByteBuffer& operator<<(int64 v) { Write(v); return *this; }
ByteBuffer& operator<<(float v) { Write(v); return *this; }
ByteBuffer& operator<<(double v) { Write(v); return *this; }
// 字串:長度 + 內容 + 結束符
ByteBuffer& operator<<(const std::string& v) {
Write<uint32>(v.size());
Append(reinterpret_cast<const uint8*>(v.data()), v.size());
return *this;
}
ByteBuffer& operator<<(const char* v) {
size_t len = v ? strlen(v) : 0;
Write<uint32>(len);
if (len) Append(reinterpret_cast<const uint8*>(v), len);
return *this;
}
// 反序列化運算子
ByteBuffer& operator>>(bool& v) { v = Read<uint8>(); return *this; }
ByteBuffer& operator>>(uint8& v) { v = Read<uint8>(); return *this; }
ByteBuffer& operator>>(uint16& v) { v = Read<uint16>(); return *this; }
ByteBuffer& operator>>(uint32& v) { v = Read<uint32>(); return *this; }
ByteBuffer& operator>>(uint64& v) { v = Read<uint64>(); return *this; }
// ... 其他型別類似 ...
ByteBuffer& operator>>(std::string& v) {
uint32 len = Read<uint32>();
v.resize(len);
if (len) Read(reinterpret_cast<uint8*>(&v[0]), len);
return *this;
}
// STL 容器支援(範例:vector)
template <typename T>
ByteBuffer& operator<<(const std::vector<T>& v) {
Write<uint32>(v.size());
for (const auto& item : v) *this << item;
return *this;
}
template <typename T>
ByteBuffer& operator>>(std::vector<T>& v) {
uint32 size = Read<uint32>();
v.resize(size);
for (auto& item : v) *this >> item;
return *this;
}
// 存取底層資料
const uint8* Data() const { return storage_.data(); }
uint8* Data() { return storage_.data(); }
size_t ReadableBytes() const { return writePos_ - readPos_; }
size_t WriteableBytes() const { return storage_.size() - writePos_; }
private:
std::vector<uint8> storage_;
size_t readPos_ = 0;
size_t writePos_ = 0;
// 端序轉換(簡化版,實務建議用 htonl/ntohl 或 byteswap)
template <typename T> T HostToNet(T v) { return EndianSwap(v); }
template <typename T> T NetToHost(T v) { return EndianSwap(v); }
};
readPos_ / writePos_ 使單一緩衝區同時支援「寫入發送」與「讀取解析」,避免資料拷貝。
// 實際專案建議使用標準函式
uint16 HostToNet(uint16 v) { return htons(v); }
uint32 HostToNet(uint32 v) { return htonl(v); }
uint64 HostToNet(uint64 v) {
return ((uint64)htonl((uint32)v) << 32) | htonl((uint32)(v >> 32));
}
不依賴結束符 \0,支援嵌入 \0 的二進位資料。長度用 uint32 支援 4GB 內容。
struct PlayerInfo {
uint32 id;
std::string name;
std::vector<uint32> itemIds;
void Serialize(ByteBuffer& buf) const {
buf << id << name << itemIds;
}
void Deserialize(ByteBuffer& buf) {
buf >> id >> name >> itemIds;
}
};
// 發送端
ByteBuffer buf;
buf << uint32(1001) // 封包 ID
<< std::string("Login") // 指令名
<< uint32(12345) // 用戶 ID
<< std::string("token_xyz");
send(socket, buf.Data(), buf.ReadableBytes(), 0);
// 接收端
ByteBuffer recvBuf;
recvBuf.Append(recvData, recvLen);
uint32 packetId; std::string cmd; uint32 uid; std::string token;
recvBuf >> packetId >> cmd >> uid >> token;
reserve()sendmsg/recvmmsg 或 io_uringByteBuffer 實例減少分配開銷ByteBuffer 以 型別安全、跨平台、可擴充 的方式解決 TCP 封包編解碼問題。核心在於:統一端序、長度前綴編碼、讀寫位置分離、STL 容器遞迴序列化。這是網路程式庫(如 Muduo、Netty、gRPC 內部)的基石元件,掌握其設計精髓有助於建構高效能、可維護的網路應用。