protobuf简介
protobuf简介
ProtocolBuffer是一种支持多平台、多语言、可扩展的的数据序列化机制,相较于XML来说,protobuf更小更快更简单,支持自定义的数据结构,用protobu编译器生成特定语言的源代码,如C++、Java、Python,目前protoBuf对主流的编程语言都提供了支持,非常方便的进行序列化和反序列化。
一、Message定义
这里简单的给出一个例子,是一个搜索请求的message格式
syntax="proto3”;//指定proto版本,默认为proto2,而且改行必须是第一行 //message包含多个种类的fields messageSearchRequest{ stringquery=1; int32page_number=2; int32result_per_page=3; }
上述例子中fields的种类都是数值型的(string和int32),当然也可以指定更加复杂的fields,比如枚举类型enum,或者是嵌套的message类型
1、分配field编号
上述例子中每个field都被分配了一个编号,这个编号是这个field的唯一标识,其中需要注意的是,标识1-15在编码的时候只占用一个字节,16-2047占用两个字节,所以为了进一步优化我们的程序,对于那种经常出现的element,建议使用1-15来作为他们的唯一标识,对那些不被经常使用的element,建议使用16-2047,编号的范围是1-2^29(19000-19999是系统预留的,不要使用)
2、field类型
message当中的field类型包含以下两种(proto3):
(1)singular
(2)repeated:该类型的field可以在message中重复使用(类似于数组),他们的顺序会被保存,通过索引进行检索,数值类型的repeated默认使用packed编码方式
3、多message结构
在一个proto文件中可以定义多个protobuf
4、reservedfield类型
在开发过程中可能会涉及到对proto文件中message各个fields的修改,可能是更新、删除某个field及其表示,这样可能会导致调用的服务失败。其中一个防止这种问题的方式是,确保你要删除的field的标识(或是名字)是reserved,具体protobuf的编译器会决定未来这个field表示能否被使用
//数字标识和命名不能在同一条语句中混合声明 messageFoo{ reserved2,15,9to11; reserved"foo","bar"; }
5、编译结果
对于C++开发者来说,使用protoc编译一个proto文件之后,会生成pb.h和http://pb.cc两个文件,具体如下图
二、数值类型
具体proto类型对应生成类中的类型可以参考官方文档
https://developers.google.com/protocol-buffers/docs/proto3#scalardevelopers.google.com/protocol-buffers/docs/proto3#scalar
三、默认值
对于string和byte类型,默认值为空;对于bool类型,默认值是false;对于数值类型,默认值是0;对于枚举类型,默认值是第一个枚举值,默认为0;对于message类型,默认值由编程语言决定;对于repeatedfield,默认值为空
四、枚举类型
当采用枚举类型的之后,枚举中的值都是预先定义好的,对于上述例子,我们可以再额外增加一个枚举类型corpus,具体如下
messageSearchRequest{ stringquery=1; int32page_number=2; int32result_per_page=3; enumCorpus{ UNIVERSAL=0; WEB=1; IMAGES=2; LOCAL=3; NEWS=4; PRODUCTS=5; VIDEO=6; } Corpuscorpus=4; }
通常枚举类型的第一个值初始化为0,而且在message中使用枚举类型,必须要给定一个为0的值,而且这个为0的值应该为第一个元素
也可以给不同的元素以相同的alias,但是需要指定optionallow_alias=true;具体如下
enumEnumAllowingAlias{ optionallow_alias=true; UNKNOWN=0; STARTED=1; RUNNING=1; } enumEnumNotAllowingAlias{ UNKNOWN=0; STARTED=1; //RUNNING=1;//UncommentingthislinewillcauseacompileerrorinsideGoogleandawarningmessageoutside. }
初次之外枚举类型不仅可以定义在message内部,也可以定义在message外部,而且在不同message中可以重用enum。
在更改枚举类型field时,为保证系统运行正常,同样可以指定reserved数字标识和命名
五、使用其他message类型
1、同文件引用
具体如下:
messageSearchResponse{ repeatedResultresults=1; } messageResult{ stringurl=1; stringtitle=2; repeatedstringsnippets=3; }
2、不同文件引用
引用其他proto文件中定义好的message类型,具体如下
import"myproject/other_protos.proto";
有时我们会对引用的proto文件进行更改,比如将其内容移动到另外一个地方,这样我们就需要对调用方import路径进行更改,当调用方非常多的时候,这种方法是非常低效的,protobuf提供一种机制,我们可以在原有位置提供一个新位置proto文件的“副本”,通过使用importpublic表示来实现,具体可以参考如下例子
//new.proto //Alldefinitionsaremovedhere //old.proto //Thisistheprotothatallclientsareimporting. importpublic"new.proto"; import"other.proto"; //client.proto import"old.proto"; //Youusedefinitionsfromold.protoandnew.proto,butnotother.proto
这时编译器就会在某些固定目录下查询import的proto文件(具体在命令行编译的时候,由-I/—proto_path指定),如果上述路径找不到,编译器会在调用路径进行查找。通常将—proto_path设置为项目的根目录,然后import的时候使用完整的路径名
3、使用proto2中的message类型
proto2中的枚举类型无法直接使用
六、嵌套类型
具体如下
messageSearchResponse{ messageResult{ stringurl=1; stringtitle=2; repeatedstringsnippets=3; } repeatedResultresults=1; }
在parentmessage之外调用嵌套的message可以用如下方式:SearchResponse.Result,嵌套结构可以更加复杂,具体如下:
messageOuter{//Level0 messageMiddleAA{//Level1 messageInner{//Level2 int64ival=1; boolbooly=2; } } messageMiddleBB{//Level1 messageInner{//Level2 int32ival=1; boolbooly=2; } } }
七、更新message类型
当现有的message已经无法满足现有业务需要,你需要更新你的message类型以支持更复杂的业务,这就涉及到向后兼容的问题了,为保证已有服务不受影响,需要遵守以下的一些规定
1、不要更改已经存在的fields的数字标识
2、如果添加新的field,利用旧代码序列化得到的message可以使用新的代码进行解析,你需要记住各个元素的默认值。新代码创建的field同样可以由旧代码进行加解析
3、field可以被删除,但是需要保证其对应的数字标识不再被使用,你可以通过加前缀的方式来重新使用这个fieldname,或者指定数字标识为reserved来避免这种情况
4、int32、int64、uint32、uint64、bool这些类型都是互相兼容的,并不会影响前向、后向兼容性
5、sint32和sint64之间是互相兼容的,但是和其他数字类型是不兼容的
6、string和bytes是互相兼容的,只要使用的是UTF-8编码
7、如果byte包含message的编码版本,则嵌套的message和bytes兼容
8、flexed32兼容sfixed32,fixed64,sfixed64
9、enum兼容int32,uint32,int64,anduint64。对于这个值在转化时,不同语言的客户端处理方式会有所不同
原文链接: