首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

TCP 網路封包序列化實戰:ByteBuffer 完整實作解析

     編輯:關於C語言

TCP 網路封包序列化實戰:ByteBuffer 完整實作解析

TCP 是位元組流協定,無訊息邊界。將結構體直接 memcpy 發送雖然簡單,但面臨記憶體對齊差異、可變長度資料不支援、跨平台端序問題三大痛點。本文實作一個生產級 ByteBuffer,支援基本型別、STL 容器、字串的序列化/反序列化,解決網路封包編解碼難題。

為什麼不直接傳 struct?

問題說明
記憶體對齊編譯器插入填充位元組,不同平台/編譯選項導致大小不一
端序差異x86 小端序,網路大端序,ARM 可配置,直接傳輸會解析錯誤
可變長資料std::string、std::vector 內含指標,memcpy 只複製指標值而非內容
版本演進結構體新增欄位會破壞舊版本相容性

序列化核心原則

  1. 固定長度型別:按位元組序寫入(網路序/大端序)
  2. 可變長度型別:先寫長度(uint32),再寫內容
  3. 容器序列化:寫入元素個數,依序序列化每個元素
  4. 讀寫位置分離: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

ByteBuffer 核心實作

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); }
};

關鍵設計細節

1. 讀寫位置分離

readPos_ / writePos_ 使單一緩衝區同時支援「寫入發送」與「讀取解析」,避免資料拷貝。

2. 端序統一為網路序(大端序)

// 實際專案建議使用標準函式
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)); 
}

3. 字串格式:長度前綴 + 二進位內容

不依賴結束符 \0,支援嵌入 \0 的二進位資料。長度用 uint32 支援 4GB 內容。

4. 擴充性:自訂型別序列化

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_uring
  • 物件池:重用 ByteBuffer 實例減少分配開銷
  • SIMD 加速:大量浮點數序列化可用 AVX2 指令集

總結

ByteBuffer 以 型別安全、跨平台、可擴充 的方式解決 TCP 封包編解碼問題。核心在於:統一端序、長度前綴編碼、讀寫位置分離、STL 容器遞迴序列化。這是網路程式庫(如 Muduo、Netty、gRPC 內部)的基石元件,掌握其設計精髓有助於建構高效能、可維護的網路應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved