Go 二进制流序列化/反序列化库

14 阅读6分钟

Bytes - Go 二进制流序列化/反序列化库

类似 Rust deku 库的 Go 语言实现,用于 TCP/UDP 字节流协议的解析和序列化。

特性

  • 使用 struct tag 描述二进制格式
  • 支持大端序和小端序
  • 支持位级操作
  • 支持条件字段
  • 支持数组、切片
  • 支持嵌套结构体
  • 支持自定义序列化/反序列化
  • 支持字段填充

安装

go get github.com/teafull/bytes

快速开始

基本使用

package main

import (
    "fmt"
    "github.com/teafull/bytes"
)

type PacketHeader struct {
    Magic   uint16 `deku:"endian=big"`
    Version uint8  `deku:"bits=4"`
    Type    uint8  `deku:"bits=4"`
    Length  uint16 `deku:"endian=big"`
}

func main() {
    // 序列化
    header := PacketHeader{
        Magic:   0x1234,
        Version: 1,
        Type:    2,
        Length:  16,
    }
    data, err := bytes.Marshal(header)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Serialized: %x\n", data)

    // 反序列化
    var decoded PacketHeader
    _, err = bytes.Unmarshal(data, &decoded)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decoded: %+v\n", decoded)
}

位字段

type BitFields struct {
    Flag1    uint8 `deku:"bits=1"`
    Flag2    uint8 `deku:"bits=1"`
    Reserved uint8 `deku:"bits=6"`
    Value    uint8
}

条件字段

type ConditionalPacket struct {
    HasOption bool  `deku:"bits=1"`
    Reserved  uint8 `deku:"bits=7"`
    Option    uint8 `deku:"cond=HasOption"`  // 仅当 HasOption 为 true 时存在
    Payload   uint16 `deku:"endian=big"`
}

动态数组

type DynamicArray struct {
    Type   uint8
    Count  uint8
    Values []uint16 `deku:"count=Count,endian=big"`
}

嵌套结构体

type Inner struct {
    Value1 uint16 `deku:"endian=big"`
    Value2 uint16 `deku:"endian=big"`
}

type Outer struct {
    Header Inner
    Length uint16 `deku:"endian=big"`
}

自定义序列化

type CustomField struct {
    Data []byte
}

func (c *CustomField) MarshalBinary() ([]byte, error) {
    // 自定义序列化逻辑
    return c.Data, nil
}

func (c *CustomField) UnmarshalBinary(data []byte) error {
    // 自定义反序列化逻辑
    c.Data = make([]byte, len(data))
    copy(c.Data, data)
    return nil
}

字段填充

type PaddedPacket struct {
    Type     uint8
    Padding  byte `deku:"pad=3"`  // 填充3个字节
    Length   uint16 `deku:"endian=big"`
}

Tag 说明

Tag说明示例
endian字节序,可选值 biglittlenativedeku:"endian=big"
bits位宽,用于位字段(1-64位)deku:"bits=4"
bytes字节宽度deku:"bytes=8"
cond条件字段,仅当条件为真时解析deku:"cond=HasFlag"
count数组/切片长度,支持表达式deku:"count=Length"deku:"count=Length+1"
skip跳过字段deku:"skip=true"
pad填充字节数deku:"pad=3"

条件表达式

支持以下格式的条件表达式:

  • 布尔字段名:HasOption
  • 等于:Field == 1
  • 不等于:Field != 0
  • 大于:Field > 5
  • 小于:Field < 10
  • 大于等于:Field >= 5
  • 小于等于:Field <= 10

计数表达式

支持以下格式的计数表达式:

  • 字段名:Count
  • 字段名加减乘除:Count + 1Length * 2
  • 直接数字:4

支持的类型

  • 整数:uint8, uint16, uint32, uint64, int8, int16, int32, int64
  • 布尔:bool
  • 数组:[N]T
  • 切片:[]T
  • 结构体:嵌套结构体
  • 字符串:string(支持固定长度和 C 字符串)

API

Marshal

将结构体序列化为字节数组:

func Marshal(v interface{}) ([]byte, error)

Unmarshal

从字节数组反序列化到结构体:

func Unmarshal(data []byte, v interface{}) (int, error)

返回读取的字节数和错误。

测试

运行测试:

go test -v

查看示例:

go test -run Example -v

性能

本库使用位级操作和反射,适合处理网络协议等二进制数据。对于性能敏感的场景,建议使用自定义序列化。

基准测试结果

测试环境:

  • OS: macOS (Darwin)
  • CPU: Apple M2 Pro (arm64)
  • Go: 1.21
  • 测试时间: 3s
序列化性能
测试ns/opB/opallocs/op
Small Packet (5 bytes)560.736213
Medium Packet (10 bytes)119656522
Large Packet (148 bytes)239097626
Bit Field (4 bytes)818.846415
Dynamic Array (22 bytes)474.630816
Nested Struct (6 bytes)609.836815
Conditional (4 bytes)618.036012
反序列化性能
测试ns/opB/opallocs/op
Small Packet (5 bytes)591.731413
Medium Packet (10 bytes)125151722
Large Packet (148 bytes)261267225
Bit Field (4 bytes)848.241615
Dynamic Array (22 bytes)665.930818
Nested Struct (6 bytes)644.532015
Conditional (4 bytes)651.031212
往返性能 (Marshal + Unmarshal)
测试ns/opB/opallocs/op
Small Packet117067726
Medium Packet2489108244
Large Packet4977164851
位操作性能
测试ns/opB/opallocs/op
Bit Write20.9100
Bit Read15.6400

性能分析

  1. 序列化性能

    • 小型数据包(5字节):~560 ns/op,适合高频场景
    • 大型数据包(148字节):~2.4 μs/op,包含128字节数组
    • 位字段操作额外开销:~818 ns/op
  2. 反序列化性能

    • 小型数据包(5字节):~590 ns/op
    • 大型数据包(148字节):~2.6 μs/op
    • 与序列化性能基本一致
  3. 内存分配

    • 小型数据包:~360 B/op,13次分配
    • 大型数据包:~976 B/op,26次分配
    • 位操作:0分配,使用预分配缓冲区
  4. 位操作性能

    • 纯位读写操作:15-21 ns/op,0分配
    • 非常高效,接近内存访问速度

运行基准测试

运行所有基准测试:

go test -bench=. -benchmem

运行特定基准测试:

go test -bench=BenchmarkMarshal -benchmem

运行基准测试并显示内存分配详情:

go test -bench=. -benchmem -benchtime=5s

性能优化建议

  1. 减少反射开销

    • 对于热点路径,考虑使用代码生成
    • 缓存反射类型信息
  2. 内存分配优化

    • 重用字节缓冲区
    • 使用对象池减少分配
  3. 位操作优化

    • 批量处理位操作
    • 减少对齐操作

许可证

MIT License

📦 项目成果

核心功能实现

基础数据类型支持

  • 整数类型:uint8/16/32/64, int8/16/32/64
  • 布尔类型:bool
  • 数组和切片
  • 嵌套结构体
  • 字符串类型

高级特性

  • 位级操作(1-64位)
  • 字节序控制(大端序/小端序/本机字节序)
  • 条件字段(基于其他字段值)
  • 动态数组(支持表达式定义长度)
  • 字段填充(自动对齐)
  • 自定义序列化(实现接口)

Struct Tag 支持

deku:"endian=big"      // 字节序
deku:"bits=4"          // 位宽
deku:"cond=HasFlag"    // 条件字段
deku:"count=Length"    // 数组长度
deku:"pad=3"           // 填充字节

项目文件结构

bytes/
├── bit_reader.go      # 位读取器
├── bit_writer.go      # 位写入器
├── types.go           # 核心类型定义
├── tags.go            # Tag 解析器
├── marshal.go         # 序列化实现
├── unmarshal.go       # 反序列化实现
├── evaluator.go       # 条件表达式求值
├── example_test.go    # 测试用例(8个)
├── examples.go        # 使用示例
├── demo/main.go       # 演示程序
├── README.md          # 完整文档
├── PROJECT_OVERVIEW.md # 项目概览
└── go.mod             # Go 模块

测试结果

  • ✅ 所有测试通过(8个测试用例)
  • ✅ 代码覆盖率:48.0%
  • ✅ 演示程序运行正常
  • ✅ 无编译错误

使用示例

type Packet struct {
    Magic   uint16 `deku:"endian=big"`
    Version uint8  `deku:"bits=4"`
    Type    uint8  `deku:"bits=4"`
    Length  uint16 `deku:"endian=big"`
}

// 序列化
data, _ := bytes.Marshal(packet)

// 反序列化
bytes.Unmarshal(data, &packet)

与 deku 库对比

特性deku (Rust)bytes (Go)状态
位级操作
条件字段
动态数组
自定义序列化
字节序支持
嵌套结构体

适用场景

  1. TCP/UDP 网络协议解析
  2. 二进制文件格式处理
  3. 通信协议实现
  4. 数据序列化与反序列化