Proto3 语法完全手册¶
Protocol Buffers(简称 Protobuf)是 Google 开源的一种语言无关、平台无关且高度可扩展的结构化数据序列化机制。当前在 gRPC 体系中,Proto3 是事实上的标准语法版本。
1. 基础结构与标量类型¶
一个标准的 .proto 文件结构如下:
syntax = "proto3"; // 必须在第一行非注释非空行声明,否则默认为 proto2
package commerce.order.v1; // 命名空间,防止命名冲突
// 针对多语言生成的代码选项
option go_package = "example.com/commerce/order/v1;orderv1";
option java_multiple_files = true;
option java_package = "com.commerce.order.v1";
message Order {
string order_id = 1; // 字符串类型
int64 user_id = 2; // 64 位整型
double total_amount = 3; // 双精度浮点
bool is_paid = 4; // 布尔类型
bytes payload = 5; // 二进制原始字节流
}
标量类型(Scalar Value Types)映射表¶
| .proto 类型 | 描述与编码考量 | C++ 映射 | Go 映射 | Python 映射 |
|---|---|---|---|---|
double |
64 位双精度浮点数(固定 8 字节) | double |
float64 |
float |
float |
32 位单精度浮点数(固定 4 字节) | float |
float32 |
float |
int32 / int64 |
变长整数(Varint 编码)。注意:负数需耗费 10 字节! | int32 / int64 |
int32 / int64 |
int |
uint32 / uint64 |
变长无符号整数(Varint 编码) | uint32 / uint64 |
uint32 / uint64 |
int |
sint32 / sint64 |
采用 ZigZag 编码。对包含大量负数的整型极具空间优势 | int32 / int64 |
int32 / int64 |
int |
fixed32 / fixed64 |
固定 4/8 字节。当数值通常大于 $2^{28}$ 时,比 Varint 更高效 | uint32 / uint64 |
uint32 / uint64 |
int |
sfixed32 / sfixed64 |
固定长度有符号整型 | int32 / int64 |
int32 / int64 |
int |
bool |
布尔型(1 字节) | bool |
bool |
bool |
string |
UTF-8 编码或 7 位 ASCII 文本 | std::string |
string |
str |
bytes |
任意长度二进制流 | std::string |
[]byte |
bytes |
2. 默认值规则(Default Values)¶
在 Proto3 中,当反序列化或未显式赋值时,字段将自动赋予语言环境对应的默认零值:
- string:空字符串
"" - bytes:空字节序列
b""或[]byte{} - bool:
false - 数值类型(int, float 等):
0 - enum 枚举:首个枚举元素(其数值必须为
0) - message 嵌套对象:语言相关的空对象指针(如 Go 中的
nil,Java 中的null) - repeated 集合:空列表
[!WARNING] 零值不占用网络传输带宽!在 Proto3 中,如果某个标量字段的值等于其默认零值,该字段不会被序列化到二进制数据流中。接收端若未读到该字段,会自动恢复为默认值。这虽然节省了带宽,但也意味着在原生 Proto3 中无法直接区分“字段未赋值”与“字段被显式赋值为零”。若需区分,可使用
optional关键字或包装类型。
3. 枚举类型(Enumerations)¶
enum PaymentStatus {
// 第一个枚举常量的值必须为 0,作为默认零值
PAYMENT_STATUS_UNSPECIFIED = 0;
PAYMENT_STATUS_PENDING = 1;
PAYMENT_STATUS_COMPLETED = 2;
PAYMENT_STATUS_FAILED = 3;
}
[!IMPORTANT] 枚举命名规范:推荐枚举以
_UNSPECIFIED = 0作为首项,并添加类型前缀,防止多枚举全局符号冲突。
4. 复合类型:Repeated、Map 与 Nested¶
① 切片/数组 (repeated)¶
表示零个或多个元素的有序列表(默认启用 Packed 编码以节省空间):
② 键值字典 (map)¶
message UserAttributes {
// 语法:map<key_type, value_type> map_field = N;
// key_type 不能是 float、double 或 bytes
map<string, string> metadata = 1;
}
③ 嵌套消息定义¶
message OrderResponse {
message OrderDetail {
string id = 1;
double price = 2;
}
OrderDetail detail = 1; // 引用内部嵌套消息
}
5. 高级类型特性¶
① 互斥字段:oneof¶
若消息中包含若干字段,但在业务逻辑中同一时刻至多只有一个字段会被赋值,可以使用 oneof。它能大幅节省内存开销:
message PaymentMethod {
string account_id = 1;
oneof channel {
CreditCard credit_card = 2;
WeChatPay wechat_pay = 3;
AliPay alipay = 4;
}
}
② 动态多态类型:google.protobuf.Any¶
允许在未提前编译引用该类型的前提下嵌入任意 Protobuf 消息。它底层包含实际序列化字节流与类型标识 URL(type_url):
import "google/protobuf/any.proto";
message ErrorResponse {
int32 code = 1;
string message = 2;
repeated google.protobuf.Any details = 3; // 携带任意类型的详细调试对象
}
③ 官方常见类型(Well-Known Types)¶
Google 官方提供了大量经过严格设计的标准库,建议在日常开发中优先复用:
- google/protobuf/timestamp.proto:精确到纳秒的 UTC 时间戳。
- google/protobuf/duration.proto:时间间隔长度。
- google/protobuf/empty.proto:无入参或无返回值的空消息体。
- google/protobuf/field_mask.proto:用于 PATCH 部分更新的字段掩码。
- google/protobuf/wrappers.proto:将基本类型(Int32Value, StringValue)包装为对象,以支持 null 语义。