Protobuf学习记录
Protocol Buffers(简称 Protobuf)是一种高效的、语言中立的序列化数据格式,广泛用于数据存储和网络通信。它通过 .proto 文件定义数据结构,并生成多种语言的代码以便操作这些数据。
安装
- git地址:https://github.com/protocolbuffers/protobuf
- 下载地址:https://github.com/protocolbuffers/protobuf/releases/download/v21.7/protoc-21.7-linux-x86_64.zip
1 | # go |
核心语法详解
示例结构
1 | syntax = "proto3"; // 必须放在文件第一行 |
版本声明
指定使用 proto3(若不声明默认 proto2):
1 | syntax = "proto3"; // 必须放在文件第一行 |
包声明
避免命名冲突,类似 Go 的package:
1 | package example; // 生成Go代码时,默认会作为Go包的一部分 |
导入其他 proto 文件
可导入其他.proto文件的定义(支持相对路径或绝对路径):
1 | import "common.proto"; // 导入同目录下的common.proto |
消息定义(Message)
消息是 Protobuf 的核心,用于定义数据结构,类似 Go 的结构体。
1 | message Person { |
- 字段编号:
- 1-15 占用 1 字节(推荐常用字段用),16-2047 占用 2 字节,不可重复,一旦使用不可修改(影响序列化兼容)。因此,开发者应该为非常频繁出现的消息元素保留数字1到15.请记住将来可能添加频繁出现的元素留出一些空间。
- 可以制定的最小字段编号是1,最大的是536870911(2^29-1)。同时,不能使用19000到19999,因为他们是为了Protocol Buffers实现保留的。
- 修饰符(proto3 中简化):
- 默认字段:单值(不可为null,有默认值)。
- repeated:表示重复字段(类似 Go 的切片[]T)。
- optional:可选字段(可显式设置为null,proto3 v3.15 + 支持)。
字段类型映射
Protobuf 类型与常用语言类型的对应关系:
| .proto | Java | Python | Go | PHP |
|---|---|---|---|---|
| double | double | float | float64 | float |
| float | float | float | float32 | float |
| int32 | int | int | int32 | integer |
| int64 | long | int/long | int64 | integer/string |
| uint32 | int | int/long | uint32 | integer |
| uint64 | long | int/long | uint64 | integer/string |
| sint32 | int | int | int32 | integer |
| sint64 | long | int/long | int64 | integer/string |
| fixed32 | int | int/long | uint32 | integer |
| fixed64 | long | int/long | uint64 | integer/string |
| sfixed32 | int | int | int32 | integer |
| sfixed64 | long | int/long | int64 | integer/string |
| bool | boolean | bool | bool | boolean |
| string | String | str/unicode | string | string |
| bytes | ByteString | str (Python 2) bytes(Python 3) | []byte | string |
枚举(Enum)
定义离散值集合,字段值必须是非负整数:
1 | enum Gender { |
- 枚举默认值为第一个字段(值为 0)。
- 可通过option allow_alias = true;允许枚举值别名。
嵌套消息
消息内部可嵌套定义其他消息:
1 | message Student { |
Oneof
表示多个字段中最多只能有一个被设置(类似 Go 的interface{}联合类型):
1 | message Result { |
- 设置 oneof 中的一个字段会自动清除其他字段的值。
MAP类型
定义键值对(类似 Go 的map[K]V):
1 | message Config { |
- 键类型只能是标量类型(int32/string等),不能是message或enum。
- 生成的 Go 代码中对应map[K]V。
服务定义(Service)
用于 RPC 通信,定义服务接口(需配合 gRPC 使用):
1 | service UserService { |
Protobuf语法选项
在 Protobuf 中,option(选项)用于配置 .proto 文件的全局行为、消息/字段的特性或代码生成规则。选项分为文件级选项、消息级选项、字段级选项、枚举级选项和服务级选项等,部分选项是 Protobuf内置的,部分是特定语言(如 Go、Java)或工具(如 gRPC)扩展的。
通用内置选项(适用于所有语言)
文件级选项
作用于整个 .proto 文件,通常放在文件开头(版本声明和包声明之后)。
| 选项 | 说明 | 示例 |
|---|---|---|
| java_package | 指定生成的 Java 代码的包路径(对 Go 无效) | option java_package = "com.example.proto"; |
| java_outer_classname | 指定生成的 Java 外部类名(对 Go 无效) | option java_outer_classname = "UserProto"; |
| java_multiple_files | 若为 true,Java 代码会按消息拆分到多个文件(默认 false) | option java_multiple_files = true; |
| objc_class_prefix | 为生成的 Objective-C 类添加前缀(对 Go 无效) | option objc_class_prefix = "EX"; |
| deprecated | 标记文件为已废弃(工具可能会警告) | option deprecated = true; |
| cc_enable_arenas | 启用 C++ Arena 内存管理(对 Go 无效) | option cc_enable_arenas = true; |
消息级选项
作用于 message 定义,影响消息的序列化 / 反序列化行为或代码生成。
| 选项 | 说明 | 示例 |
|---|---|---|
| message_set_wire_format | 仅用于 proto2,指定消息使用 MessageSet 格式(不推荐) | option message_set_wire_format = true; |
| deprecated | 标记消息为已废弃 | message OldMessage { option deprecated = true; } |
| no_standard_descriptor_accessor | 禁用生成标准描述符访问器(C++/Java 用) | option no_standard_descriptor_accessor = true; |
字段级选项
作用于消息中的字段,控制字段的序列化规则或代码生成细节。
| 选项 | 说明 | 示例 |
|---|---|---|
| deprecated | 标记字段为已废弃(使用时工具会警告) | int32 old_field = 1 [deprecated = true]; |
| packed | 仅 proto2 有效,对 repeated 基本类型启用打包编码(节省空间) | repeated int32 ids = 1 [packed = true]; |
| default | 仅 proto2 有效,指定字段的默认值(proto3 已移除,默认值由类型决定) | string name = 1 [default = "unknown"];(proto2) |
| json_name | 指定字段在 JSON 序列化时的名称(覆盖默认的下划线转驼峰) | string user_name = 1 [json_name = "username"]; |
枚举级选项
作用于 enum 定义,控制枚举的行为。
| 选项 | 说明 | 示例 |
|---|---|---|
| allow_alias | 允许枚举值使用别名(即不同名称对应相同值) | enum Status { option allow_alias = true; OK = 0; SUCCESS = 0; } |
| deprecated | 标记枚举为已废弃 | enum OldEnum { option deprecated = true; } |
服务级选项
作用于 service 定义,主要用于 RPC 框架(如 gRPC)。
| 选项 | 说明 | 示例 |
|---|---|---|
| deprecated | 标记服务为已废弃 | service OldService { option deprecated = true; } |
Go 语言特定选项(protoc-gen-go 扩展)
Go 语言的 Protobuf 代码生成器(protoc-gen-go)提供了一系列扩展选项,需通过导入 google/protobuf/descriptor.proto 或直接使用特定选项。
go_package(文件级)
最常用选项,指定生成的 Go 代码的包路径和包名,避免包名冲突。格式:”导入路径;包名”(包名可选,默认取导入路径的最后一段)。
1 | syntax = "proto3"; |
若不指定,默认包名为 .proto 的 package 声明,可能导致冲突。
字段级 Go 选项
通过[(go.field) = { ... }] 配置字段的 Go 代码生成规则(需导入google.golang.org/protobuf/types/descriptorpb/go_field.proto)。
| 选项 | 说明 | 示例 |
|---|---|---|
| name | 自定义字段在 Go 中的名称(覆盖默认的下划线转驼峰) | string user_name = 1 [(go.field) = { name: "UserName" }]; |
| type | 自定义字段的 Go 类型(需兼容原类型) | int32 count = 1 [(go.field) = { type: "int" }]; |
| json_tag | 自定义字段的 JSON 序列化标签(覆盖默认) | string name = 1 [(go.field) = { json_tag: "user_name,omitempty" }]; |
消息级 Go 选项
通过[(go.message) = { ... }]配置消息的 Go 代码生成规则。
| 选项 | 说明 | 示例 |
|---|---|---|
| name | 自定义消息在 Go 中的类型名 | message User { option (go.message) = { name: "UserInfo" }; } |
gRPC 相关选项(protoc-gen-go-grpc 扩展)
用于配置 gRPC 服务代码生成,需配合 service 定义使用。
服务级 gRPC 选项
通过 [(grpc.service) = { ... }] 配置服务生成规则。
| 选项 | 说明 | 示例 |
|---|---|---|
| name | 自定义 gRPC 服务在 Go 中的接口名 | service UserService { option (grpc.service) = { name: "UserServer" }; } |
方法级 gRPC 选项
通过 [(grpc.method) = { ... }] 配置服务方法的生成规则。
| 选项 | 说明 | 示例 |
|---|---|---|
| idempotency_level | 指定方法的幂等性级别(影响 gRPC 重试策略) | rpc UpdateUser(UpdateReq) returns (UpdateResp) { option (grpc.method) = { idempotency_level: IDEMPOTENT }; } |
| name | 自定义方法在 Go 中的函数名 | rpc GetUser(GetReq) returns (GetResp) { option (grpc.method) = { name: "FetchUser" }; } |
其他常用扩展选项
JSON 映射选项
控制 Protobuf 与 JSON 的互转规则(适用于所有支持 JSON 的语言)。
| 选项 | 说明 | 示例 |
|---|---|---|
| json_name(字段级) | 自定义 JSON 字段名(前文已提及) | string user_name = 1 [json_name = "username"]; |
| ignore_unknown(文件级) | JSON 反序列化时忽略未知字段 | option json_enable_ignore_unknown = true; |
验证选项(第三方扩展)
通过第三方库(如 protoc-gen-validate)添加字段验证规则,需导入对应的扩展 proto。
1 | import "validate/validate.proto"; |
Go 语言中 Protobuf 的使用命令
1 | protoc [选项] --go_out=[输出目录] --go-grpc_out=[输出目录] 输入文件.proto |
--go_out:指定protoc-gen-go的输出目录及参数(如 Go 模块路径)。--go-grpc_out:指定protoc-gen-go-grpc的输出目录及参数(若定义了 service)。--proto_path(缩写-I):指定.proto文件的导入路径(默认当前目录)。