Google Protocol Buffers(简称 Protobuf)是一套“数据结构定义 + 代码生成 + 二进制序列化”工具。
先在 .proto 文件里约定字段,再生成 C#、Java、Go 等语言的读写代码,让客户端和服务端遵守同一份协议。
数据定义
以这份玩家数据为例:
1 | {"chips":150,"money":12.5,"bonus":0} |
不带空格的 UTF-8 JSON 是 34 字节。
JSON 是文本格式,字段名、数值、字符串以及标点,最终都编码成文本字节传输。
比如 {“chips”:150} 的chips字段,除了字段名本身,还需要两个字节传输引号。 连同冒号,”chips”: 共占 8 字节。
这里 150 是 JSON 数值,占 3 字节。
接收方解析后,才会把数值文本 150 转换为内存中的数字;它在 JSON 传输时并不是一个固定 4 字节的整数。
类似地,12.5 传的是四个字符 1、2、.、5,JSON 本身也不区分 float 和 double,由接收程序决定怎么存储。
你的protobuf数据可以定义为:
1 | syntax = "proto3"; |
这里的 1、2、3 是固定的字段编号,不是字段值。
按上面的协议,Protobuf 二进制通常只需要 8 字节:
| 内容 | JSON | Protobuf |
|---|---|---|
| 字段标识 | 每次发送 "chips" 等字段名 |
发送字段编号和编码类型组成的标签 |
整数 150 |
3 个文本字节 | 2 个变长整数字节 |
浮点数 12.5 |
文本表示 | float 固定 4 字节 |
bonus = 0 |
仍包含字段名和值 | 此例中,proto3 默认值省略 |
| 结构 | 花括号、引号、冒号、逗号 | 无须这些文本符号 |
这个例子里的 8 字节,chips 占 3 字节,money 占 5 字节,bonus 不占空间。(每个字段标签占1字节)
Protobuf的标签不包含字段名,也不一定直接包含字段长度。标签包含这个字段的部分元数据:字段编号和底层编码方式。
整条消息的结束位置则由外层提供,例如 HTTP 响应体的长度或嵌套消息的长度前缀。Protobuf 本身没有一个通用的“消息结束符”,所以在连续的 TCP 字节流中发送多条消息时,需要另外约定每条消息的长度。
数据结构
字段编号与字段名的对应关系需要额外存储。
Protobuf 的关键是:把这份映射提前放在客户端和服务端的协议定义、生成代码里,而不是每次发送数据时重复传输。
所以它用“双方提前约定数据结构”的成本,换取“每条消息更小”的收益。
但缩小比例取决于数据:大量重复字段名、数字、列表通常收益明显;很长的字符串内容仍然要传。
Protobuf 不是压缩算法,也不是加密算法,还可以与 gzip 等压缩一起使用。
若用 Protobuf 的 JSON 输出模式,传输的仍是 JSON,拿不到上述二进制收益。
接入时需要注意这几件事:
- 客户端和服务端共同支持协议。 如果服务端仍然只接受表单和 JSON,单独换 Unity 序列化方式无法使用。
- 网络层改为处理字节。 通常用 UploadHandlerRaw 发送,读取 downloadHandler.data 接收;Protobuf 也可以继续通过普通 HTTP 使用,不要求 gRPC。
- Unity 可以使用 Google.Protobuf,但接入后仍需验证程序集引用和目标平台的 IL2CPP 构建
字段映射
开发时,.proto 文件就是协议定义:
| 字段编号 | 字段名 | 数据类型 |
|---|---|---|
| 1 | chips | int32 |
| 2 | money | float |
| 3 | bonus | float |
通过 protoc 编译器,可以为客户端生成 C# 类,为服务端生成对应语言的类。
字段名还可能保存在生成的描述信息中,用于反射、JSON 转换等功能。这些内容属于程序代码和元数据,不需要跟着每条网络消息发送。
可以理解为双方事先约定:
1 | “编号 1 表示筹码,编号 2 表示余额,编号 3 表示奖励余额。” |
此后每次通信只发送“编号和值”。
JSON 则会在每次通信时重复发送:
1 | “chips 的值是 150,money 的值是 12.5……” |
因此,Protobuf 并没有消灭映射,而是把映射从每条消息里移到了双方程序里。
以及二进制里并不是只有字段值,Protobuf 不能直接把 150 和 12.5 连起来,因为接收方需要知道:
- 这是哪个字段?
- 这个字段的值占多少字节?
- 读完后下一个字段在哪里?
所以每个实际发送的字段,基本结构是:
1 | 字段标签 tag + 字段值 |
对于字符串等可变长度的数据,则是:
1 | 字段标签 tag + 内容长度 + 内容 |