[2023-11] 更新:本文整理自 Proto 官网,可以学习 go 如何使用 proto,但部分内容已过时,请注意。
要创建地址簿应用程序,您需要从一个 .proto
文件开始。文件中的定义 .proto
很简单:为要序列化的每个数据结构添加一条 _ 消息 _,然后为消息中的每个字段指定名称和类型。在我们的示例中,.proto
定义消息的文件是 addressbook.proto
.
该 .proto
文件以包声明开头,这有助于防止不同项目之间的命名冲突。
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
该 go_package
选项定义包的导入路径,其中将包含为此文件生成的所有代码。 Go 包名称将是导入路径的最后一个路径组成部分。例如,我们的示例将使用包名称“tutorialpb”。
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
接下来,您有消息定义。消息只是包含一组类型化字段的聚合。许多标准简单数据类型都可用作字段类型,包括 bool
、int32
、float
、double
和 string
。您还可以通过使用其他消息类型作为字段类型来向消息添加进一步的结构。
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
enum PhoneType {
PHONE_TYPE_UNSPECIFIED = 0;
PHONE_TYPE_MOBILE = 1;
PHONE_TYPE_HOME = 2;
PHONE_TYPE_WORK = 3;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
在上面的例子中,Person
消息包含 PhoneNumber
消息,而 AddressBook
消息包含 Person
消息。您甚至可以定义嵌套在其他消息中的消息类型 - 正如您所看到的,类型 PhoneNumber
是在 Person
。
PhoneType
使用了 enum
类型,这是一种枚举类型。
如果字段为 repeated
,则该字段可以重复任意次数(包括零次)。重复字段视为切片。
每个元素上的“= 1”、“= 2”标记标识该字段在二进制编码中使用的唯一“标记”。标签编号 1-15 需要比编号更高的标签少一个字节进行编码,因此作为一种优化,您可以决定将这些标签用于常用或重复的元素,而将标签 16 和更高的标签留给不太常用的可选元素。重复字段中的每个元素都需要重新编码标签编号,因此重复字段特别适合此优化。
如果未设置字段值, 则使用 默认值:数字类型为零,字符串为空字符串,布尔值为 false。对于嵌入消息,默认值始终是消息的“默认实例”或“原型”,其未设置任何字段。调用访问器来获取尚未显式设置的字段的值始终返回该字段的默认值。
您将在Protocol Buffer Language Guide 中找到编写文件的完整指南 .proto
- 包括所有可能的字段类型 。不过,不要去寻找类似于类继承的设施——协议缓冲区不会这样做。
编译
现在您已经有了 .proto
,接下来需要做的就是生成读取和写入 AddressBook
(以及 Person
和 PhoneNumber
)消息所需的类。为此,您需要编译 .proto
:
-
安装编译器, 请下载软件包。
-
运行以下命令安装 Go protocol buffers 插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
编译器插件
protoc-gen-go
将安装在 中$GOBIN
,默认为$GOPATH/bin
.它必须在你的$PATH
协议编译器中protoc
才能找到它。 -
现在运行编译器,指定源目录(应用程序源代码所在的位置 - 如果不提供值,则使用当前目录)、目标目录(您希望生成的代码所在的位置;通常与 相同
$SRC_DIR
) ,以及通往您的 的路径.proto
。在这种情况下,您将调用:protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
因为您需要 Go 代码,所以您使用该
--go_out
选项 - 为其他支持的语言提供了类似的选项。
protoc -I . helloworld.proto --go_out=plugins=grpc:.
addressbook.pb.go
在您指定的目标目录中生成。
API
生成为 addressbook.pb.go
您提供以下有用的类型:
AddressBook
具有字段的结构People
。Person
具有Name
、Id
和Email
字段的结构Phones
。- 一个
Person_PhoneNumber
结构体,包含Number
和字段Type
。 - 为枚举中的每个值定义的类型
Person_PhoneType
和值Person.PhoneType
。
您可以在Go 生成代码指南 中阅读更多有关生成内容的详细信息 ,但在大多数情况下,您可以将它们视为完全普通的 Go 类型。
list_people
以下是命令单元测试 中的示例 ,说明如何创建 Person 的实例:
p := pb.Person{
Id: 1234,
Name: "John Doe",
Email: "jdoe@example.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_PHONE_TYPE_HOME},
},
}
写入消息
使用协议缓冲区的全部目的是序列化数据,以便可以在其他地方解析它。在 Go 中,您可以使用 proto
库的 Marshal 函数来序列化协议缓冲区数据。指向协议缓冲区消息的指针 struct
实现该 proto.Message
接口。调用 proto.Marshal
返回协议缓冲区,以其有线格式编码。例如,我们在 add_person
命令 中使用这个函数:
book := &pb.AddressBook{}
// ...
// Write the new address book back to disk.
out, err := proto.Marshal(book)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
log.Fatalln("Failed to write address book:", err)
}
读取消息
要解析编码消息,您可以使用 proto
库的 Unmarshal 函数。调用此函数会将数据解析 in
为协议缓冲区并将结果放入 中 book
。因此,要解析 list_people
命令 中的文件 ,我们使用:
// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
log.Fatalln("Failed to parse address book:", err)
}
扩展
在您发布使用 protocol buffer 的代码之后,那么您需要遵循一些规则。在新版本的 protocol buffer 中:
- 不得更改任何现有字段的标签号。
- 可以删除字段。
- 可以添加新字段,但必须使用新的标签号(即在此协议缓冲区中从未使用过的标签号,甚至删除的字段也不行)。