个人觉得,有谷歌翻译,百度翻译。加上自己的理解。自个看看官方文档也还不错。
下面98%是谷歌翻译跟百度翻译的。剩余2%是我自己按照自己的理解。
免责声明:粗糙翻译,随便看看哈。想纠正的。留言即可。本人水平有限。
Protocol Buffer Basics: Go
通过创建一个简单的示例应用程序,它向您展示如何:
- 在.proto文件中定义消息格式。
- 使用协议缓冲区编译器。
- 使用Go协议缓冲区API来编写和读取消息。
为何使用protobuf
假设我们的示例是一个非常简单的“地址簿”应用程序,可以在文件中读取和写入人员的联系人详细信息。地址簿中的每个人都有姓名,ID,电子邮件地址和联系电话号码。
那么序列化和恢复这样结构(序列化与反序列化)的几种方法:
使用gobs序列化Go数据结构。这是Go特定环境中的一个很好的解决方案,但如果您需要与为其他平台编写的应用程序共享数据,它将无法正常工作。
您可以发明一种特殊的方法将数据项编码为单个字符串 - 例如将4个整数编码为“12:3:-23:67”。这是一种简单而灵活的方法,虽然它确实需要编写一次性编码和解析代码,并且解析会产生较小的运行时成本。这最适合编码非常简单的数据。
将数据序列化为XML。这种方法非常有吸引力,因为XML(有点)是人类可读的,并且有许多语言的绑定库。如果您想与其他应用程序/项目共享数据,这可能是一个不错的选择。然而,XML是众所周知的空间密集型,并且编码/解码它会对应用程序造成巨大的性能损失。此外,导航XML DOM树比通常在类中导航简单字段要复杂得多。
协议缓冲区是一种灵活、高效、自动化的解决方案,可以精确地解决这个问题。使用协议缓冲区,可以编写要存储的数据结构的.proto描述。由此,协议缓冲区编译器创建了一个类,该类用有效的二进制格式实现协议缓冲区数据的自动编码和解析。生成的类为组成协议缓冲区的字段提供getter和setter,并负责将协议缓冲区作为一个单元读写的详细信息。重要的是,协议缓冲区格式支持随着时间的推移扩展格式的思想,这样代码仍然可以读取用旧格式编码的数据。
示例代码https://github.com/protocolbuffers/protobuf/tree/master/examples
定义你的protocol 格式
要创建通讯簿应用程序,您需要从.proto文件开始。.proto文件中的定义很简单:为要序列化的每个数据结构添加消息,然后为消息中的每个字段指定名称和类型。在我们的示例中,定义消息的.proto文件是addressbook.proto。
.proto文件以包声明开头,这有助于防止不同项目之间的命名冲突。
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto"
在go中,包名就是作为Go的包名。除非你指定了一个go_package(编译时可以指定go_package)。你要是提供了go_package ,你定义的包名要避免与非Go语言的Protobuf 的命名空间的冲突。
vscode 可以按照proto3的插件,这样编辑起来比较方便。
下面,你有了你的消息定义。一个消息是包含一组类型字段的聚合。许多标准的简单数据类型都可以作为字段类型,包括bool,int32, float,double,和string。您还可以使用其他消息类型作为字段类型,为邮件添加更多结构。
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type =2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
message AddressBook {
repeated Person people = 1;
}
在上面的示例中,Person消息包含 PhoneNumber消息,而AddressBook消息包含Person消息。您甚至可以定义嵌套在其他消息中的消息类型 - 如您所见, PhoneNumber类型在内部定义Person。您还可以定义enum,如果你希望你的领域之一,有预定义的值列表中的一个类型-在这里你要指定一个电话号码可以是一个MOBILE,HOME或 WORK
每个元素上的“= 1”,“= 2”标记标识该字段在二进制编码中使用的唯一“标记”。
如果未设置字段值, 则使用默认值:数字类型为零,字符串为空字符串,bools为false。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,其中没有设置其字段。调用访问器以获取尚未显式设置的字段的值始终返回该字段的默认值。
如果是字段repeated
,则字段可以重复任意次数(包括零)。重复值的顺序将保留在协议缓冲区中。将重复字段视为动态大小的数组。
编译protobuf
下载protoc(windows。下载,把protoc的bin目录放到环境变量里。mac可以使用brew install 去安装)。
生成go所需要的pb.go文件需要 go get -u github.com/golang/protobuf/protoc-gen-go。
现在运行编译器,指定源目录(应用程序的源代码所在的位置 - 如果不提供值,则使用当前目录),目标目录(您希望生成的代码在哪里;通常同$SRC_DIR) ,以指定你要编译的.proto。
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
输出go用的文件用go_out,其他语言有其他语言的选项。
protobuf API
我在我工程的目录下抄写了官网代码,并在当前目录下执行protoc addressbook.proto --go_out=./
生成的addressbook.pb.go为你提供了下面可用的类型:
AddreBook包含了People
Person里面有Name,Id,Email,Phones.
Person_PhoneNumber包含Number 和Type
类型 Person_PhoneType和Person.PhoneType枚举
去看下官方示例。
如何创建Person实例
p := pb.Person{
Id: 1234,
Name: "John Doe",
Email: "jdoe@example.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_HOME},
},
}
写一个消息
使用协议缓冲区的全部目的是序列化您的数据,以便可以在其他地方解析它。在Go中,您使用proto
库的Marshal 函数来序列化协议缓冲区数据。指向协议缓冲区消息的指针struct
实现proto.Message
接口。调用proto.Marshal
返回以其有线格式编码的protobuf。例如,我们在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 函数。调用此方法将数据解析buf
为协议缓冲区并将结果放入pb
。因此,要在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)
}
扩展protobuf
当你代码上线之后,避免不了修改proto文件的定义。你需要注意一些兼容性规则。在你的新版本代码中:
- 不能更改现有字段的编号。
- 可以删除字段
- 可以添加字段,新增字段使用新的编号(从没用过的,哪怕是给删除了字段使用过的)
遵循了这些规则,旧代码在读消息的时候会忽略你的新代码。对于删除的字段,旧代码也会给其默认值,删除的repeated字段将为空。新代码也
旧消息中不会出现新字段,因此需要使用默认值执行合理操作。使用特定于类型的 默认值 :对于字符串,默认值为空字符串。对于布尔值,默认值为false。对于数字类型,默认值为零。
示例代码跑一跑
官方的代码示例。https://github.com/protocolbuffers/protobuf
首先,我go get下来了。然后用vscode 打开了这个项目。
在proto文件目录下我创建了一个文件夹tutorial
(当然文档里面有个Makefile会帮你做这件事情,但我windows不好使,手动来)。以便于生成pb.go文件放入其中(不然 list_people_test.go 里面引入的pb找不到)。
第一例子是以下是list_people
命令的单元测试示例 ,说明如何创建Person实例:
go test -v list_people_test.go list_people.go
(-v是看命令都干了些啥)
写一条消息(Writing a Message)
go run add_person.go addressbook.data
然后就输入数据写入到指定的文件中。
Enter person ID number: 123434
Enter name: mike
Enter email address (blank for none): mike@gmail.com
Enter a phone number (or leave blank to finish): 178933
Is this a mobile, home, or work phone? home
Enter a phone number (or leave blank to finish): 98783
Is this a mobile, home, or work phone? work
Enter a phone number (or leave blank to finish): 123333
Is this a mobile, home, or work phone? mobile
打开addressbook.data,看看内容
"
�mike�{��mike@gmail.com"
�178933
;
�mike������mike@gmail.com"
�178933��"
�98783��"
�123333
读一条消息(Reading a Message)
go run list_people.go addressbook.data
Person ID: 123
Name: mike
E-mail address: mike@gmail.com
Mobile phone #: 178933
Person ID: 123434
Name: mike
E-mail address: mike@gmail.com
Home phone #: 178933
Work phone #: 98783
我的思考
剥去了丝丝神秘?就像我当年刚入行让我给移动端写接口,我一头雾水什么是接口?前辈说ajax写过吧。写过……_
json序列化反序列化知道吧。json改成proto。传的参数(类型是Message)是proto文件生成的pb.go文件中的Message。也可以看看Message类型是什么?
type Message interface {
Reset()
String() string
ProtoMessage()
}
只要实现了这三个接口的,就是Message类型了(想想Duck Type)。
我在我工程下,简单的练习了下。
p := pb.Person{
Id: 1234,
Name: "Mike",
Email: "mike@gmail.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_HOME},
},
}
book := &pb.AddressBook{
People: []*pb.Person{&p},
}
out, err := proto.Marshal(book)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}
fmt.Println(out)
book1 := &pb.AddressBook{}
proto.Unmarshal(out, book1)
fmt.Printf("%s\n", string(out))
fmt.Printf("%v", book1)
输出
[10 39 10 4 77 105 107 101 16 210 9 26 14 109 105 107 101 64 103 109 97 105 108 46 99 111 109 34 12 10 8 53 53 53 45 52 51 50 49 16 1]
'
�Mike�� ��mike@gmail.com"
555-4321��
people:<name:"Mike" id:1234 email:"mike@gmail.com" phones:<number:"555-4321" type:HOME > >
参考资料:
《go test 测试用例那些事》https://www.cnblogs.com/li-peng/p/10036468.html