当前位置:首页 > 文章列表 > Golang > Go教程 > Go binary 怎样按大端序读写固定结构

Go binary 怎样按大端序读写固定结构

来源:17golang原创 2026-10-22 00:39:45 0浏览 收藏

我在处理固定格式的文件头和网络报文时,最容易踩的坑不是 API 不会用,而是写端和读端对字节序、字段宽度的约定没有落到同一份结构上。Go 的 encoding/binary 已经提供了明确的 binary.BigEndian,只要结构体只包含固定大小的字段,就可以用一套规则完成编码和解码。

官方文档:https://pkg.go.dev/encoding/binary

本文要点
  • 固定结构优先使用 binary.Write 和 binary.Read,两端传入同一个 binary.BigEndian。
  • 结构体不会按 C 语言那样自动写入内存填充,字段会按声明顺序编码;空白字段可以显式占位。
  • 读取不完整数据时,读到一部分后遇到结尾通常应关注 io.ErrUnexpectedEOF。

一、先把协议字段写成固定结构

假设报文由一个 2 字节魔数、一个 1 字节版本、一个 4 字节负载长度和 4 字节固定负载组成。这里的重点是把协议宽度写成 Go 的定长类型,而不是使用 int、string 或不定长切片。

package main

import "encoding/binary"

// Packet 描述一个固定宽度的二进制报文头和负载。
type Packet struct {
	Magic      uint16  // 2 字节:用于识别协议
	Version    uint8   // 1 字节:用于判断格式版本
	PayloadLen uint32  // 4 字节:声明负载长度
	Payload    [4]byte // 4 字节:示例中的固定负载
}

var order binary.ByteOrder = binary.BigEndian

这份结构一共占 11 个协议字节:uint16 占 2 字节,uint8 占 1 字节,uint32 占 4 字节,数组再占 4 字节。binary.Write 和 binary.Read 关注的是这些固定大小字段的顺序,不会把 Go 结构体在内存中的地址布局直接写进文件。

Go binary 大端序固定结构中的协议约定和字段关系说明图
图1:固定结构说明图,展示 BigEndian 规则与 Packet 字段的静态归属关系,不是截图或运行证据。

二、用 binary.Write 按大端序编码

编码时把同一个字节序对象传给 binary.Write。对于需要先在内存中组装、再交给网络层或文件层的场景,bytes.Buffer 足够直观;真正写文件时也可以把它替换成文件对象。

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

func encodePacket(packet Packet) ([]byte, error) {
	var buf bytes.Buffer
	// 明确指定 BigEndian,避免依赖运行机器的本地字节序。
	if err := binary.Write(&buf, binary.BigEndian, packet); err != nil {
		return nil, fmt.Errorf("编码 Packet 失败: %w", err)
	}
	return buf.Bytes(), nil
}

func main() {
	packet := Packet{
		Magic:      0x1234,
		Version:    1,
		PayloadLen: 4,
		Payload:    [4]byte{'G', 'o', '!', '!'},
	}
	data, err := encodePacket(packet)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 输出只是示例打印,实际项目可交给文件或网络写入器。
	fmt.Printf("% x\n", data)
}

在大端序下,0x1234 的两个字节会按 12 34 写出,PayloadLen 的 4 字节也从高位到低位排列。数组字段会原样写入。这个约定和机器是小端还是大端没有关系,协议规则已经由调用处固定。

三、用 binary.Read 按同一规则解码

解码时要保持三件事一致:读取器、字节序和目标结构体。目标必须是指向可写变量的指针;如果只传结构体值,读取函数没有地方保存结果。

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

func decodePacket(data []byte) (Packet, error) {
	var packet Packet
	// Reader 会按字段顺序填充 packet,字节序必须与写入端一致。
	if err := binary.Read(bytes.NewReader(data), binary.BigEndian, &packet); err != nil {
		return Packet{}, fmt.Errorf("解码 Packet 失败: %w", err)
	}
	return packet, nil
}

func main() {
	data := []byte{0x12, 0x34, 0x01, 0x00, 0x00, 0x00, 0x04, 'G', 'o', '!', '!'}
	packet, err := decodePacket(data)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 读出后的字段已恢复为 Go 的定长类型。
	fmt.Printf("magic=%#x version=%d length=%d payload=%q\n", packet.Magic, packet.Version, packet.PayloadLen, packet.Payload)
}

如果把读取端误写成 binary.LittleEndian,同一组字节中的多字节字段会被解释成另一个数值;单字节的 Version 和逐字节的 Payload 看起来却可能正常,这也是字节序问题容易被漏掉的原因。

四、在短数据和字段变更处设置边界

固定结构的大小可以用 binary.Size 计算。读取前先判断输入长度,能够把“报文被截断”和“报文内容非法”分开;即使不预检,也要保留 binary.Read 返回的错误。

package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
)

func decodeComplete(data []byte) (Packet, error) {
	want := binary.Size(Packet{})
	// 先判断长度,避免把明显的截断输入交给后续业务逻辑。
	if len(data) 

文档定义了一个有用的区别:如果一次字节都没有读到,可以得到 io.EOF;已经读到部分字段后才遇到输入结束,则应按 io.ErrUnexpectedEOF 处理。协议升级时新增字段也必须同步修改写端、读端和长度约定,否则旧版本可能把新字段误当成下一段数据。

binary.Write binary.Read 与 Packet 和短数据错误边界的静态关系说明图
图2:读写边界说明图,展示 binary.Write、binary.Read、Packet 和 io.ErrUnexpectedEOF 的静态关系,不是运行截图。

五、按场景选择结构体还是手动切片

固定结构的优势是字段清楚、读写代码短,适合协议头、文件头和需要与其他语言约定字段顺序的格式。若结构中加入了字符串、指针、接口或变长切片,就不再是这个简单模型,应该把变长部分的长度和字节内容单独处理。

对于需要严格控制每一段长度的解析器,可以直接使用 binary.BigEndian.Uint16、Uint32 读取切片,并在每次读取前检查剩余长度:

func readHeader(data []byte) (magic uint16, version uint8, length uint32, err error) {
	// 先检查每个字段需要的字节数,防止切片越界。
	if len(data) 

这个手动切片版本适合需要逐字段校验、兼容多个版本或只读取头部的场景,但切片下标更容易随着协议变更失步。我的取舍是:结构稳定时用结构体集中表达协议,字段经常演进或需要部分读取时再切换到显式偏移,并把偏移常量和长度检查放在同一个解析函数中。

六、发布前的固定清单

检查项建议常见问题
字节序读写两端都传 binary.BigEndian一端默认使用 LittleEndian
字段类型使用 uint16、uint32 等定长类型使用 int 导致协议宽度不明确
结构布局按声明顺序定义字段,空白字段只用于明确占位把 Go 内存对齐误当成协议填充
输入长度用 binary.Size 或显式偏移检查长度短数据进入业务层后才暴露
错误处理保留并分类 io.EOF、io.ErrUnexpectedEOF忽略读取错误后继续使用零值

如果只是读写一个固定宽度结构,binary.Read 和 binary.Write 已经足够;如果格式有变长字段,则把“固定头部”和“变长负载”拆开,先按大端序读长度,再按长度读取负载,边界会更容易维护。

相关问题

binary.Read 能直接读取 string 吗?

固定长度的字节数组可以读取,之后再按约定的编码转换成字符串。不要把不定长 string 当作固定结构字段直接交给它。

为什么单字节字段看不出大小端错误?

大小端只会改变多字节数值的排列,uint8 或原样字节数组没有可交换的高低字节,所以要用多字节字段的已知值核对规则。

数据比固定结构更长时怎么办?

先读取固定结构,再根据其中的长度字段读取负载;不要为了“读完”而把未知尾部直接塞进结构体。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go gzip 怎样给 HTTP 流增加压缩Go gzip 怎样给 HTTP 流增加压缩
上一篇
Go gzip 怎样给 HTTP 流增加压缩
Go filepath 怎样跨平台拼接路径
下一篇
Go filepath 怎样跨平台拼接路径
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    582次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    620次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    654次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    618次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    435次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码