📅 2024年11月29日
🏆 ProtoBuf
Protocol Buffers 是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。
protocal buffer
相比于 xml
,json
这类字符串类型传输体积更小、速度更快,而且可以使用多语言,他将数据通过二进制编码进行传输
使用 Protobuf
,可以先定义 req/res
的结构和各字段的类型、字段等信息,然后使用 Protobuf
提供的编译器生成对应的代码,用于序列化和反序列化数据,Protobuf
可以将定义的结构根据指定语言来进行编译生成对应语言可以识别的代码和文件,s
相比于 XML
和 JSON
,Protobuf
有以下几个优势:
- 更小的数据量:
Protobuf
的二进制编码通常只有XML
和JSON
的 1/3 到 1/10 左右,因此在网络传输和存储数据时可以节省带宽和存储空间。 - 更快的序列化和反序列化速度:由于
Protobuf
使用二进制格式,所以序列化和反序列化速度比XML
和JSON
快得多。 - 跨语言:
Protobuf
支持多种编程语言,可以使用不同的编程语言来编写客户端和服务端。这种跨语言的特性使得Protobuf
受到很多开发者的欢迎(JSON
也是如此)。 - 易于维护可扩展:
Protobuf
使用.proto
文件定义数据模型和数据格式,这种文件比XML
和JSON
更容易阅读和维护,且可以在不破坏原有协议的基础上,轻松添加或删除字段,实现版本升级和兼容性。
🍪 开始 protobuf
之旅
1️⃣ 安装 proto
在使用protobuf之前需要先安装protobuf的编译器,我使用的
windows
,在 protobuf
的github
上下载解压之后配置到 PATH
环境变量即可,这部分我就不写了,在 cmd
或者 prowershell
中查看是否安装成功
PS G:\learn_code\gRPC\protobuf_test> protoc --version
libprotoc 29.0
此外还需要安装
go
代码生成器使用如下命令安装:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
2️⃣ 编写 proto
文件
在使用
protobuf
之前需要先写一个 .proto
文件,在文件内定义你需要传输的数据结构,它的写法类似 java
的类或者 go
的结构体
我按照官网给的实验编写如下代码:
// 定义是proto3还是proto2
syntax = "proto3";
// 指定 protobuf 包名,防止有相同类名的 message 定义
package tutorial;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/protocolbuffers/protobuf/examples/go/phone";
// 像定义go结构一样定义消息结构
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;
}
// AddresBook包含Persion消息
message AddressBook {
repeated Person people = 1;
}
3️⃣ 文件内容解析
🏢 https://protobuf.com.cn/programming-guides/proto3/
⭐️头部全局定义
syntax = "proto3";
指定 Protobuf 版本为版本 3(最新版本)也可以指定为proto2
package tutorial;;
指定Protobuf
包名,防止有相同类名的message
(一个message
表示一个请求结构体)定义,这个包名是生成的类中所用到的一些信息的前缀,并非类所在包。option go_package =
生成的类所在包。
⭐️消息结构具体定义
使用protoc编译器来生成go文件时,生成的 Go 字段名称始终使用驼峰式命名即使
.proto
文件中的字段名称使用下划线的小写
- 第一个字母大写以导出。如果第一个字符是下划线,则将其删除并添加一个大写 X。
- 如果内部下划线后跟小写字母,则删除下划线,并将后面的字母大写
message Person
定一个了一个 Person
类(结构体)。
Person 类中的字段被 optional
修饰,被 optional
修饰说明字段可以不赋值。
- 修饰符
optional
表示可选字段,可以不赋值。 - 修饰符
repeated
表示数据重复多个,如数组,如 List。 - 修饰符
required
表示必要字段,必须给值,否则会报错RuntimeException
,但是在 Protobuf 版本 3 中被移除。即使在版本 2 中也应该慎用,因为一旦定义,很难更改。 map
:这是一个配对键/值字段类型。有关此字段类型的更多信息,请参阅 Maps。- 如果没有应用显式字段标签,则假定默认字段标签,称为“隐式字段存在”。(您无法将字段显式设置为此状态。)格式良好的消息可以有零个或一个此字段(但不能多于一个)。您也无法确定此类型的字段是否已从线路解析。隐式存在字段将序列化到线路,除非它是默认值。有关此主题的更多信息,请参阅 字段存在。
对于具有多个值的 optional
字段,仅使用最后一个字段
⭐️字段类型定义
协议缓冲区编译器为消息中定义的每个字段生成一个结构字段。此字段的确切性质取决于其类型以及它是单数、重复、映射还是 oneof
字段,修饰符后面紧跟的是字段类型,如 int32
、string
。常用的类型如下:
int32、int64、uint32、uint64
:整数类型,包括有符号和无符号类型。float、double
:浮点数类型。bool
:布尔类型,只有两个值,true 和 false。string
:字符串类型。bytes
:二进制数据类型。enum
:枚举类型,枚举值可以是整数或字符串。message
:消息类型,可以嵌套其他消息类型,类似于结构体。
在定义字段时必须为消息定义中的每个字段指定 1
到 536,870,911
之间的一个数字,而且有如下限制:
- 给定的数字必须在该消息的所有字段中唯一。
- 字段编号
19,000
至19,999
为Protocol Buffers
实现保留。如果您在消息中使用这些保留字段编号之一,协议缓冲区编译器将发出警告。 - 您不能使用任何先前保留的字段编号或已分配给扩展的任何字段编号
⭐️字段后面的
=1,=2
是作为序列化后的二进制编码中的字段的对应标签,因为 Protobuf
消息在序列化后是不包含字段信息的,只有对应的字段序号,所以节省了空间。也因此,范围为 1 到 15 的字段编号需要一个字节进行编码。范围为 16 到 2047 的字段编号需要两个字节,1-15 比 16 会少一个字节,所以尽量使用 1-15 来指定常用字段。而且不能在你使用此消息之后改动,因为你修改就相当于删除原有的字段并创建一个新的字段
⭐️在上述文件中我定义了多个
mesage
,message
也可以嵌套,但是在一个文件中最好不要定义多个 message
,会使得生成的文件过于膨胀
⭐️ 删除消息
⚠️ 当不再需要某个字段且已从客户端代码中删除所有引用时,可以从消息中删除该字段定义。但是,必须保留已删除的字段号。如果不保留字段号,开发人员将来有可能重新使用该字段号
如果通过完全删除字段或将其注释掉来更新消息类型,则未来的开发人员可以在对类型进行自己的更新时重新使用该字段号。这可能会导致严重问题,如重新使用字段号的后果中所述。
为确保这种情况不会发生,请将已删除的字段号添加到 reserved
列表中。为确保消息的 JSON
和 TextFormat
实例仍可解析,还应将已删除的字段名称添加到 reserved
列表中。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
⭐️ 更新消息类型
https://protobuf.com.cn/programming-guides/proto3/#updating
- 不要更改任何现有字段的字段编号。“更改”字段编号等同于删除该字段并添加具有相同类型的新字段
- 添加新字段的新代码可以解析旧消息类型,旧消息类型也可以解析新消息类型,但是会忽略掉新添加字段
int32
、uint32
、int64
、uint64
和bool
都兼容——这意味着您可以将一个字段从这些类型之一更改为另一种类型,而不会破坏向前或向后兼容性sint32
和sint64
彼此兼容,但不与其他整数类型兼容。fixed32
与sfixed32
兼容,fixed64
与sfixed64
兼容。只要字节是有效的 UTF-8,string
和bytes
就是兼容的。如果字节包含消息的编码版本,则嵌入式消息与bytes
兼容。
⭐️ any
Any
消息类型可以保存任意 proto3 消息,Any
类型将具有特殊的 pack()
和 unpack()
访问器,在不同语言中有不同的方法
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
⭐️ oneof
如果消息体中有有多个字段,但是只允许设置一个字段就可以使用
oneof
来设置一个字段,当设置其中一个字段的时候会驱逐其他字段用来节省内存,设置 oneof
的任何成员会自动清除所有其他成员。您可以使用特殊的 case()
或 WhichOneof()
方法(取决于您选择的语言)来检查 oneof
中设置的值(如果有)
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
oneof
的注意事项 https://protobuf.com.cn/programming-guides/proto3/#oneof-features
⭐️ maps
protobuf
支持创建映射关系
map<string, Project> projects = 3;
key_type
可以是任何整数或字符串类型(因此,除了浮点类型和 bytes
之外的任何 标量 类型)。请注意,枚举和 proto 消息对于 key_type
都是无效的。value_type
可以是任何类型,除了另一个 maps
maps
特性: https://protobuf.dev/programming-guides/proto3/#maps
⭐️ 枚举值选项
支持枚举值选项。您可以使用 deprecated
选项来表示某个值不应该再被使用。您还可以使用扩展名创建自定义选项。
以下示例显示了添加这些选项的语法
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
4️⃣ 编译 proto
文件
使用 Protobuf 提供的编译器,可以将
.proto
文件编译成各种语言的代码文件(如 Java、C++、Python 等)
进入编写 proto
文件的目录执行如下命令
PS G:\learn_code\gRPC\protobuf_test> protoc --go_out=. addressbook.proto
在目录下就会生成如下代码 github.com/protocolbuffers/protobuf/examples/go/tutorialpb/addressbook.pb.go
查看代码内容,解析可以查看官方文档如下:
https://protobuf.dev/reference/go/go-generated/
// 定义是proto3还是proto2
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.2
// protoc v5.29.0
// source: addressbook.proto
// 指定 protobuf 包名,防止有相同类名的 message 定义
package tutorialpb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 枚举
type PhoneType int32
const (
PhoneType_PHONE_TYPE_UNSPECIFIED PhoneType = 0
PhoneType_PHONE_TYPE_MOBILE PhoneType = 1
PhoneType_PHONE_TYPE_HOME PhoneType = 2
PhoneType_PHONE_TYPE_WORK PhoneType = 3
)
// Enum value maps for PhoneType.
var (
PhoneType_name = map[int32]string{
0: "PHONE_TYPE_UNSPECIFIED",
1: "PHONE_TYPE_MOBILE",
2: "PHONE_TYPE_HOME",
3: "PHONE_TYPE_WORK",
}
PhoneType_value = map[string]int32{
"PHONE_TYPE_UNSPECIFIED": 0,
"PHONE_TYPE_MOBILE": 1,
"PHONE_TYPE_HOME": 2,
"PHONE_TYPE_WORK": 3,
}
)
func (x PhoneType) Enum() *PhoneType {
p := new(PhoneType)
*p = x
return p
}
func (x PhoneType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PhoneType) Descriptor() protoreflect.EnumDescriptor {
return file_addressbook_proto_enumTypes[0].Descriptor()
}
func (PhoneType) Type() protoreflect.EnumType {
return &file_addressbook_proto_enumTypes[0]
}
func (x PhoneType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PhoneType.Descriptor instead.
func (PhoneType) EnumDescriptor() ([]byte, []int) {
return file_addressbook_proto_rawDescGZIP(), []int{0}
}
// 像定义go结构一样定义消息结构
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Unique ID number for this person.
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
LastUpdated *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
}
func (x *Person) Reset() {
*x = Person{}
mi := &file_addressbook_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Person) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Person) ProtoMessage() {}
func (x *Person) ProtoReflect() protoreflect.Message {
mi := &file_addressbook_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Person.ProtoReflect.Descriptor instead.
func (*Person) Descriptor() ([]byte, []int) {
return file_addressbook_proto_rawDescGZIP(), []int{0}
}
func (x *Person) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Person) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Person) GetEmail() string {
if x != nil {
return x.Email
}
return ""
}
func (x *Person) GetPhones() []*Person_PhoneNumber {
if x != nil {
return x.Phones
}
return nil
}
func (x *Person) GetLastUpdated() *timestamppb.Timestamp {
if x != nil {
return x.LastUpdated
}
return nil
}
// AddresBook包含Persion消息
type AddressBook struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
People []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"`
}
func (x *AddressBook) Reset() {
*x = AddressBook{}
mi := &file_addressbook_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AddressBook) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddressBook) ProtoMessage() {}
func (x *AddressBook) ProtoReflect() protoreflect.Message {
mi := &file_addressbook_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddressBook.ProtoReflect.Descriptor instead.
func (*AddressBook) Descriptor() ([]byte, []int) {
return file_addressbook_proto_rawDescGZIP(), []int{1}
}
func (x *AddressBook) GetPeople() []*Person {
if x != nil {
return x.People
}
return nil
}
// 嵌套消息结构 电话号码和电话号类型
type Person_PhoneNumber struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
Type PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=tutorial.PhoneType" json:"type,omitempty"`
}
func (x *Person_PhoneNumber) Reset() {
*x = Person_PhoneNumber{}
mi := &file_addressbook_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Person_PhoneNumber) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Person_PhoneNumber) ProtoMessage() {}
func (x *Person_PhoneNumber) ProtoReflect() protoreflect.Message {
mi := &file_addressbook_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Person_PhoneNumber.ProtoReflect.Descriptor instead.
func (*Person_PhoneNumber) Descriptor() ([]byte, []int) {
return file_addressbook_proto_rawDescGZIP(), []int{0, 0}
}
func (x *Person_PhoneNumber) GetNumber() string {
if x != nil {
return x.Number
}
return ""
}
func (x *Person_PhoneNumber) GetType() PhoneType {
if x != nil {
return x.Type
}
return PhoneType_PHONE_TYPE_UNSPECIFIED
}
var File_addressbook_proto protoreflect.FileDescriptor
var file_addressbook_proto_rawDesc = []byte{
0x0a, 0x11, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x08, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x1a, 0x1f, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87,
0x02, 0x0a, 0x06, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a,
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d,
0x61, 0x69, 0x6c, 0x12, 0x34, 0x0a, 0x06, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50,
0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x52, 0x06, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x6c, 0x61, 0x73,
0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6c, 0x61, 0x73,
0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x4e, 0x0a, 0x0b, 0x50, 0x68, 0x6f, 0x6e,
0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12,
0x27, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e,
0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x54, 0x79,
0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x37, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x28, 0x0a, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69,
0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
0x65, 0x2a, 0x68, 0x0a, 0x09, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a,
0x0a, 0x16, 0x50, 0x48, 0x4f, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53,
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x48,
0x4f, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x4f, 0x42, 0x49, 0x4c, 0x45, 0x10,
0x01, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x48, 0x4f, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x48, 0x4f, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x48, 0x4f, 0x4e, 0x45, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x03, 0x42, 0x3c, 0x5a, 0x3a, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
0x6f, 0x6c, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x74,
0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_addressbook_proto_rawDescOnce sync.Once
file_addressbook_proto_rawDescData = file_addressbook_proto_rawDesc
)
func file_addressbook_proto_rawDescGZIP() []byte {
file_addressbook_proto_rawDescOnce.Do(func() {
file_addressbook_proto_rawDescData = protoimpl.X.CompressGZIP(file_addressbook_proto_rawDescData)
})
return file_addressbook_proto_rawDescData
}
var file_addressbook_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_addressbook_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_addressbook_proto_goTypes = []any{
(PhoneType)(0), // 0: tutorial.PhoneType
(*Person)(nil), // 1: tutorial.Person
(*AddressBook)(nil), // 2: tutorial.AddressBook
(*Person_PhoneNumber)(nil), // 3: tutorial.Person.PhoneNumber
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_addressbook_proto_depIdxs = []int32{
3, // 0: tutorial.Person.phones:type_name -> tutorial.Person.PhoneNumber
4, // 1: tutorial.Person.last_updated:type_name -> google.protobuf.Timestamp
1, // 2: tutorial.AddressBook.people:type_name -> tutorial.Person
0, // 3: tutorial.Person.PhoneNumber.type:type_name -> tutorial.PhoneType
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_addressbook_proto_init() }
func file_addressbook_proto_init() {
if File_addressbook_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_addressbook_proto_rawDesc,
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_addressbook_proto_goTypes,
DependencyIndexes: file_addressbook_proto_depIdxs,
EnumInfos: file_addressbook_proto_enumTypes,
MessageInfos: file_addressbook_proto_msgTypes,
}.Build()
File_addressbook_proto = out.File
file_addressbook_proto_rawDesc = nil
file_addressbook_proto_goTypes = nil
file_addressbook_proto_depIdxs = nil
}
⭐️ 创建序列化对象
package main
import (
"fmt"
phone "tanc.fun/protobuf_tset/github.com/protocolbuffers/protobuf/examples/go/phone"
)
func main() {
// 创建一个人
person := phone.Person{
Name: "tan",
Id: 0,
Email: "xxx@xxx.com",
Phones: []*phone.Person_PhoneNumber{
{
Number: "1234567",
Type: 3,
},
},
LastUpdated: nil,
}
// 将这个人加入到我的通讯录
addrBook := phone.AddressBook{
People: []*phone.Person{
&person,
},
}
// 输出我的通讯录
fmt.Println(addrBook.String())
// 查看我通讯录里面的人
fmt.Println(addrBook.GetPeople())
}
5️⃣ 序列化和反序列化
- 序列化:将内存中的数据对象序列化为二进制数据,可以用于网络传输或存储等场景。
- 反序列化:将二进制数据反序列化成内存中的数据对象,可以用于数据处理和业务逻辑。
在go中需要使用到
proto
包来实现
package main
import (
"fmt"
"google.golang.org/protobuf/proto"
phone "tanc.fun/protobuf_tset/github.com/protocolbuffers/protobuf/examples/go/phone"
)
func main() {
person := phone.Person{
Name: "tan",
Id: 0,
Email: "xxx@xxx.com",
Phones: []*phone.Person_PhoneNumber{
{
Number: "1234567",
Type: 3,
},
},
LastUpdated: nil,
}
addrBook := phone.AddressBook{
People: []*phone.Person{
&person,
},
}
// 序列化
serializedData, err := proto.Marshal(&addrBook)
if err != nil {
fmt.Printf("序列化失败:%v\n", err)
return
}
fmt.Printf("序列化后的数据:%v\n", serializedData)
// 反序列化
var newAddrBook phone.AddressBook
err = proto.Unmarshal(serializedData, &newAddrBook)
if err!= nil {
fmt.Printf("反序列化失败:%v\n", err)
return
}
fmt.Printf("反序列化后的AddressBook:%+v\n", newAddrBook)
}
6️⃣ 和 json
比较
protobuf
要比 json
传输大小要小很多,请看如下实验:
package main
import (
"encoding/json"
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/runtime/protoimpl"
"google.golang.org/protobuf/types/known/timestamppb"
phone "tanc.fun/protobuf_tset/github.com/protocolbuffers/protobuf/examples/go/phone"
)
// 1:1 将生成的pb文件中的结构体复制过来
type AddressBook struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
People []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"`
}
type Person_PhoneNumber struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
Type PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=tutorial.PhoneType" json:"type,omitempty"`
}
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Unique ID number for this person.
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
LastUpdated *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
}
type PhoneType int32
func main() {
// protobuf对象创建
person := phone.Person{
Name: "tan",
Id: 0,
Email: "xxx@xxx.com",
Phones: []*phone.Person_PhoneNumber{
{
Number: "1234567",
Type: 3,
},
},
LastUpdated: nil,
}
addrBook := phone.AddressBook{
People: []*phone.Person{
&person,
},
}
// 本地结构体创建
person2 := Person{
Name: "tan",
Id: 0,
Email: "xxx@xxx.com",
Phones: []*Person_PhoneNumber{
{
Number: "1234567",
Type: 3,
},
},
LastUpdated: nil,
}
addrBook2 := AddressBook{
People: []*Person{
&person2,
},
}
// json序列化本地结构体
jsonM, _ := json.Marshal(addrBook2)
// protobuf序列化
protobufM, _ := proto.Marshal(&addrBook)
fmt.Printf("probuf序列化后的大小:%v\n", len(protobufM))
fmt.Printf("json序列化后的大小:%v\n", len(jsonM))
}
//输出:
probuf序列化后的大小:33
json序列化后的大小:90