Golang的序列化-ProtoBuf篇
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.protobuf概述
Protobuf是Protocol Buffers的简称,它是Google公司用C语言(因此很多语法借鉴C语法特性)开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。 它很适合做数据存储或RPC数据交换格式。 可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了C++、Java、Python三种语言的API,其它语言需要安装相关插件才能使用。 Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。 这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。需要了解以下两点 1>.protobuf是类似与json一样的数据描述语言(数据格式); 2>.protobuf非常适合于RPC数据交换格式; 接着我们来看一下protobuf的优势和劣势: 优势: 1>序列化后体积相比Json和XML很小,适合网络传输 2>支持跨平台多语言 3>消息格式升级和兼容性还不错 4>序列化反序列化速度很快,快于Json的处理速度 劣势: 1>应用不够广(相比xml和json) 2>二进制格式导致可读性差 3>缺乏自描述 博主推荐阅读: https://developers.google.cn/protocol-buffers/docs/gotutorial
二.protobuf安装
1>.下载protobuf软件包
博主推荐阅读: https://github.com/protocolbuffers/protobuf/releases
2>.配置环境变量
3>.安装Go的编译插件
执行以下命令安装插件:
go get -u github.com/golang/protobuf/protoc-gen-go
如下图所示,安装成功后会在%GOPATH%\bin目录生成一个编译工具"protoc-gen-go.exe"
三.protobuf的简单语法
1>.参考文档
博主推荐阅读: https://developers.google.com/protocol-buffers/docs/reference/go-generated
2>.编写简单的protobuf案例(文件名后缀以".proto"结尾)
//protobuf默认支持的版本是2.x,现在一般使用3.x版本,所以需要手动指定版本号,如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。 syntax = "proto3"; //指定包名,package关键字指明当前是mypb包生(成go文件之后和Go的包名保持一致,但是如果定义了"option go_package"参数,则package的参数自动失效) package mypb; //.proto文件应包含一个go_package选项,用于指定包含所生成代码的Go软件包的完整导入路径(最后一次"bar"就是生成go文件的包名),官方在未来的发行版本会支持哟; option go_package ="example.com/foo/bar"; /* 通过message关键定义传输数据的格式,,类似于go语言中的结构体,是包含一系列类 型数据的集合。 许多标准的简单数据类型都可以作为字段类型,包括 bool , int32 , float , double ,和 string 。也可以使用其他message类型作为字段类型。 */ message People{ /* 注意哈,这里的"1"表示字段是1,类似于数据库中表的主键id等于1,主键不能重复,标识位数据不能重复。该成员编码时用1代替名字。 我们知道,在json中是通过成员的名字来绑定对应的数据,但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据。 综上所述,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。 */ string name = 1; //需要注意的是,标识为不能使用19000-19999(系统预留位) int32 age = 2; //结构体嵌套,比如我们嵌套一个Student结构体 Student s = 3; //使用数组 repeated string phone = 4; } /* message的格式说明如下: 消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式 (字段修饰符)数据类型 字段名称 = 唯一的编号标签值; 唯一的编号标签: 代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消 息字段。 编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。 注释格式: 向.proto文件添加注释,可以使用C/C++/java/Go风格的双斜杠或者段落注释语法格式。 message常见的数据类型与go中类型对比: .proto类型 Go类型 介绍 double float64 64位浮点数 float float32 32位浮点数 int32 int32 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值, 请改用sint32。 int64 int64 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值, 请改用sint64。 uint32 uint32 使用可变长度编码。 uint64 uint64 使用可变长度编码。 sint32 int32 使用可变长度编码。符号整型值。这些比常规int32s编码负数更有效。 sint64 int64 使用可变长度编码。符号整型值。这些比常规int64s编码负数更有效。 fixed32 uint32 总是四字节。如果值通常大于228,则比uint 32更有效 fixed64 uint64 总是八字节。如果值通常大于256,则比uint64更有效 sfixed32 int32 总是四字节。 sfixed64 int64 总是八字节。 bool bool 布尔类型 string string 字符串必须始终包含UTF - 8编码或7位ASCII文本 bytes []byte 可以包含任意字节序列 */ message Student{ string name = 1; int32 age = 5; }
3>.基于protobuf文件进行编译生成对应的go文件
protoc --go_out=. demo.proto
//protobuf默认支持的版本是2.x,现在一般使用3.x版本,所以需要手动指定版本号,如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。 // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.21.0 // protoc v3.11.4 // source: demo.proto //指定包名,package关键字指明当前是mypb包生(成go文件之后和Go的包名保持一致,但是如果定义了"option go_package"参数,则package的参数失效) package bar import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" 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) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // //通过message关键定义传输数据的格式,,类似于go语言中的结构体,是包含一系列类 型数据的集合。 //许多标准的简单数据类型都可以作为字段类型,包括 bool , int32 , float , double ,和 string 。也可以使用其他message类型作为字段类型。 type People struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // //注意哈,这里的"1"表示字段是1,类似于数据库中表的主键id等于1,主键不能重复,标识位数据不能重复。该成员编码时用1代替名字。 //我们知道,在json中是通过成员的名字来绑定对应的数据,但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据。 //综上所述,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。 Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` //需要注意的是,标识为不能使用19000-19999(系统预留位) Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` //结构体嵌套,比如我们嵌套一个Student结构体 S *Student `protobuf:"bytes,3,opt,name=s,proto3" json:"s,omitempty"` //使用数组 Phone []string `protobuf:"bytes,4,rep,name=phone,proto3" json:"phone,omitempty"` } func (x *People) Reset() { *x = People{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *People) String() string { return protoimpl.X.MessageStringOf(x) } func (*People) ProtoMessage() {} func (x *People) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use People.ProtoReflect.Descriptor instead. func (*People) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{0} } func (x *People) GetName() string { if x != nil { return x.Name } return "" } func (x *People) GetAge() int32 { if x != nil { return x.Age } return 0 } func (x *People) GetS() *Student { if x != nil { return x.S } return nil } func (x *People) GetPhone() []string { if x != nil { return x.Phone } return nil } // //message的格式说明如下: //消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式 //(字段修饰符)数据类型 字段名称 = 唯一的编号标签值; // //唯一的编号标签: //代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消 息字段。 //编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。 // //注释格式: //向.proto文件添加注释,可以使用C/C++/java/Go风格的双斜杠或者段落注释语法格式。 // //message常见的数据类型与go中类型对比: //.proto类型 Go类型 介绍 //double float64 64位浮点数 //float float32 32位浮点数 //int32 int32 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值, 请改用sint32。 //int64 int64 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值, 请改用sint64。 //uint32 uint32 使用可变长度编码。 //uint64 uint64 使用可变长度编码。 //sint32 int32 使用可变长度编码。符号整型值。这些比常规int32s编码负数更有效。 //sint64 int64 使用可变长度编码。符号整型值。这些比常规int64s编码负数更有效。 //fixed32 uint32 总是四字节。如果值通常大于228,则比uint 32更有效 //fixed64 uint64 总是八字节。如果值通常大于256,则比uint64更有效 //sfixed32 int32 总是四字节。 //sfixed64 int64 总是八字节。 //bool bool 布尔类型 //string string 字符串必须始终包含UTF - 8编码或7位ASCII文本 //bytes []byte 可以包含任意字节序列 type Student struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Age int32 `protobuf:"varint,5,opt,name=age,proto3" json:"age,omitempty"` } func (x *Student) Reset() { *x = Student{} if protoimpl.UnsafeEnabled { mi := &file_demo_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Student) String() string { return protoimpl.X.MessageStringOf(x) } func (*Student) ProtoMessage() {} func (x *Student) ProtoReflect() protoreflect.Message { mi := &file_demo_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Student.ProtoReflect.Descriptor instead. func (*Student) Descriptor() ([]byte, []int) { return file_demo_proto_rawDescGZIP(), []int{1} } func (x *Student) GetName() string { if x != nil { return x.Name } return "" } func (x *Student) GetAge() int32 { if x != nil { return x.Age } return 0 } var File_demo_proto protoreflect.FileDescriptor var file_demo_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x79, 0x70, 0x62, 0x22, 0x61, 0x0a, 0x06, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x01, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x79, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x01, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x22, 0x2f, 0x0a, 0x07, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x42, 0x15, 0x5a, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x6f, 0x6f, 0x2f, 0x62, 0x61, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_demo_proto_rawDescOnce sync.Once file_demo_proto_rawDescData = file_demo_proto_rawDesc ) func file_demo_proto_rawDescGZIP() []byte { file_demo_proto_rawDescOnce.Do(func() { file_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_demo_proto_rawDescData) }) return file_demo_proto_rawDescData } var file_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_demo_proto_goTypes = []interface{}{ (*People)(nil), // 0: mypb.People (*Student)(nil), // 1: mypb.Student } var file_demo_proto_depIdxs = []int32{ 1, // 0: mypb.People.s:type_name -> mypb.Student 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_demo_proto_init() } func file_demo_proto_init() { if File_demo_proto != nil { return } if !protoimpl.UnsafeEnabled { file_demo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*People); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_demo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Student); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_demo_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_demo_proto_goTypes, DependencyIndexes: file_demo_proto_depIdxs, MessageInfos: file_demo_proto_msgTypes, }.Build() File_demo_proto = out.File file_demo_proto_rawDesc = nil file_demo_proto_goTypes = nil file_demo_proto_depIdxs = nil }