protobuf 语法浅析
protobuf 语法浅析
hgfgoodcreate 发表于1年前
protobuf 语法浅析
  • 发表于 1年前
  • 阅读 195
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

Protobuf

为什么用Protobuf Buffer

跨语言平台编程,使用SOAP的话,该方式是使用xml的方式传输,会大大增加网络的IO,而且xml的解析复杂,降低报文的解析性能。

定义一个Protobuf消息

message LogonReqMessage{
	required int64 actID=1;
	required string passwd=2;
}

关键说明:

  1. message是消息的关键字
  2. LogonReqMessage是消息名字,相当于java的类名
  3. required前缀表示该字段未必要字段,序列化前后必须赋值的字段。protobuf还存在两个类似的关键字optionalrepeated主要用于表示数组字段。
  4. int64和string表示长整型和字符串类型的消息字段,在protobuf中存在一张类型对照表,即protobuf类型与其他语言的类型对照。
  5. actID和passwd分别表示消息的字段名,等同于java中的域名变量名。
  6. 标签数字12表示不同的字段在序列化前后的二进制中的布局位置。在本例中,passwd字段编码后的数据一定位于actID后,该值在同一个message中不能重复。对于protobuf而言,标签1到15的字段在编码的时候是可以得到优化的(标签值和类型信息只占一个byte,标签范围16到2047占两个byte),protobuf可支持的字段数量是$2^{29}-1$。因此,应该将repeated类型的字段标签未与1~15,节省编码后的字节数量。

定义第二个protobuf消息

enum UserStatus{
	OFFLINE=0;
	ONLINE=1;
}

message UserInfo{
	required int64 actID=1;
	required string name=2;
	required UserStatus=3;
}

关键说明:

  1. enum是枚举类型的关键字,等同于java里面的enum
  2. UserStatus为枚举的名字
  3. 和java一样枚举之间的分隔符是分好;而不是逗号,
  4. OFFLINEONLINE表示枚举值
  5. 01表示枚举所对应的实际整型值,可为任意整型值,无需从0开始。

定义第三个protobuf消息

enum UserStatus{
	OFFLINE=0;
	ONLINE=1;
}

message UserInfo{
	required int64 actID=1;
	required string name=2;
	required UserStatus=3;
}

message LogonRespMessage{
	required LoginResult logonResult = 1;
	required UserInfo userInfo = 2;
}

关键说明:

  1. LogonRespMessage消息定义中包含另外一个消息类型作为其字段,如UserInfo userInfo
  2. 上例的UserInfo和LogonRespMessage被定义在同一个.proto文件中,那么怎么包含其他proto文件中的message呢?ProtoBuf提供关键字import将其他proto文件的message引入到当前proto文件中。例如Import "myproject/CommonMessages.proto"

限定符号

  1. 每个消息必须有一个字段是required类型的字段。
  2. 每个消息包含0个或多个optional类型的字段。
  3. repeated表示的字段可以包含0个或多个数据,有别于java的数组,因为java数组中至少包含一个元素
  4. 如果打算在原有消息协议中添加新的字段,同时保证老的字段能正常的读取写入,那么新添加的字段必须是optional或者repeated类型的。

proto类型对照表

.proto Type|Notes|C++ Type|Java Type -|-|-|- double||double|double float||float|float int32|"Uses variable-length encoding. Inefficient for encoding negative numbers ¨C if your field is likely to have negative values use sint32 instead."|int32|int int64|"Uses variable-length encoding. Inefficient for encoding negative numbers ¨C if your field is likely to have negative values use sint64 instead."|int64|long uint32|Uses variable-length encoding.|uint32|int uint64|Uses variable-length encoding.|uint64|long sint32|Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.|int32|int sint64|Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.|int64|long fixed32|Always four bytes. More efficient than uint32 if values are often greater than 228.|uint32|int fixed64|Always eight bytes. More efficient than uint64 if values are often greater than 256.|uint64|long sfixed32|Always four bytes.|int32|int sfixed64|Always eight bytes.|int64|long bool||bool|boolean string|A string must always contain UTF-8 encoded or 7-bit ASCII text.|string|String bytes|May contain any arbitrary sequence of bytes.|string|ByteString

protobuf消息升级原则

  1. 不要修改已经存在字段的标签号
  2. 任何新添加的字段必须是optional或者repeated限定符,否则无法保证新老程序在互传消息的时候消息的兼容性。
  3. 在原有消息中,不能移除已经存在的required字段,optionalrepeated类型字段可以移除,但是他们之前使用的标签不能使用了。
  4. int32uint32int64uint64bool等类型之间是兼容的,sint32sint64是兼容的,stirngbyte是兼容的,fixed32sfixed32是兼容的,fixed64sfixed64是兼容的,如果想修改原有字段类型,为了保证兼容性,只能将其修改为原有类型兼容的类型,否则打破新老消息格式的兼容性。
  5. optionalrepeated限定符是相互兼容的。

packages

可以在.proto文件中定义包名,如:package abc.lypgone,该包名生成c++时,替换成名字空间,而java为java的包名。

options

protobuf 在.proto文件中定义一些常用的选项,这样protobuf可以帮助我们生成更匹配的目标语言代码。

protobuf的内置选项分为三个等级:

  1. 文件级,这样的选项影响当前文件的所有消息和枚举。
  2. 消息级,这样的选项仅影响某个消息及其包含的所有字段。
  3. 字段级,这样的选项近影响某个字段。

常用的protobuf选项有:

  1. option java_package="com.companyname.projectname"; java_package 是文件级的选项,指定让生成的java代码的包名为该选项的值;与此同时,输出的文件也自动输出到对应包目录下(上例的com/companyname/projectname目录下),该选项对C++没有影响。
  2. option java_outer_classname="LYPhoneMessage"; java_outer_classname是文件级别的选项,指定生成的java代码的外部类名称。如果没有指定,java代码的外部类名称为当前文件的文件名部分,同时将文件名转为驼峰格式,如my_project.proto,那么该文件的外部类名为MyProject,该选项对C++代码无影响。

注意,由于一个java文件只能有一个外部类或者外部接口,所以.proto文件中的message定义的消息均为外部类的内部类,这样才能将这些消息定义到一个文件中。c++没有此限制。

  1. option optimize_for = LITE_RUNTIME; optimize_for 是文件级选项,是protobuf优化选项。该选项又分为3个等级:
    1. SPEED:表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间
    2. CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
    3. LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar

对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message

  1. [pack = true]: 因为历史原因,对于数值型的repeated字段,如int32、int64等,在编码时并没有得到很好的优化,然而在新近版本的Protocol Buffer中,可通过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:repeated int32 samples = 4 [packed=true]

注:该选项仅适用于2.3.0以上的Protocol Buffer

  1. [default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:optional int32 result_per_page = 3 [default = 10]。

命令行编译工具

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

这里将给出上述命令的参数解释。

  1. protoc为Protocol Buffer提供的命令行编译工具
  2. --proto_path等同于-I选项,主要用于指定待编译的.proto消息定义文件所在的目录,该选项可以被同时指定多个。
  3. --cpp_out选项表示生成C++代码--java_out表示生成Java代码--python_out则表示生成Python代码,其后的目录为生成后的代码所存放的目录。
  4. path/to/file.proto表示待编译的消息定义文件

注:对于C++而言,通过Protocol Buffer编译工具,可以将每个.proto文件生成出一对.h和.cc的C++代码文件。生成后的文件可以直接加载到应用程序所在的工程项目中。如:MyMessage.proto生成的文件为MyMessage.pb.h和MyMessage.pb.cc。

标签: protobuf java
共有 人打赏支持
粉丝 9
博文 51
码字总数 117706
×
hgfgoodcreate
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: