汉扬编程 C语言入门 ProtocolBuffer V3 入门

ProtocolBuffer V3 入门

Proto3入门本文基于Google提供的ProtolBuffer LanguageGuide英文文档: ProtolBuffer3 Language Guide

ProtocolBuffer V3 入门

ProtoBuf的API文档

ProtocolBuffer V3 入门

定义一个message

ProtocolBuffer V3 入门

首先以一个简单的例子开头:比如查百度,那么需要一个查询语句:query,还有查询的页面号:page_number,然后就是查询的每一页的结果数:result_per_page。 这样就有三个字段:query,page_number和result_per_page。 那么这个消息(message)定义如下:

ProtocolBuffer V3 入门

/*选中语法格式proto3,也就是ProtocolBuffer的版本3*/syntax = \”proto3\”;/*定义一个消息,消息名字为SearchRequest*/message SearchRequest{ /*键值对,每个字段则需要字段名和具体的类型*/ string query = 1; int32 page_number = 2; int32 result_per_page = 3;}复制代码对于每一个字段,必须指定具体的类型

ProtocolBuffer V3 入门

除了一个基础类型(字符串string,整型int32等)还可以定义其他综合类型,如枚举类型和其他的message类型。后面将列出。

ProtocolBuffer V3 入门

分配字段号

ProtocolBuffer V3 入门

在上面定义的SearchRequest消息中,对于每个字段,都有唯一标识的编号。这些字段号在消息(message)的二进制格式中唯一识别,在该消息(message)投入到使用后不应该被更改。 在ProtocolBuffer中将消息序列化为二进制后,对于1~15编号的字段,只需要一个字节编码,对于16~2047则需要两个字节。所以,对于把经常使用的字段元素编号到1~15中,并且预留(reserved)几位以便于以后扩展。 字段号的范围为:1~536870911(2^29-1)。其中19000~19999为ProtocolBuffer自己预留(reserved)的字段号不能使用。其他都可以自己使用。当然,自己预留(reserved)的编号在后续扩展也不能使用。对于预留(reserved)的后面将讲到。

ProtocolBuffer V3 入门

指定字段规则

消息(message)的字段可以使用两种规则描述(proto2与proto3不同):

单一的(singular):0个或1个,不用在字段定义中指出。重复的(repeated):0个到多个,需要在字段定义中指出。 看如下例子:一个人,只有一个正式的名字(在刚出生的时候名字还没登记),但是他可以有多个外号,也可以没有。 syntax = \”proto3\”; message Person{ string name = 1; repeated string nickname = 2; }复制代码添加更多的消息类型

多个消息类型可以在一个.proto文件中定义。 比如在上面SearchRequest中添加一个SearchResponse。

message SearchRequest{ string query = 1; int32 page_number = 2; int32 result_per_page = 3;}message SearchResponse{ repeated string result = 1; int32 page_number = 2;}复制代码注释

在.proto文件中注释为C/C++风格,用//注释单方,或/**/注释多行。

预留(reserved)字段

前面提到ProtocolBuffer自己预留(reserved)的字段号19000~19999。 可以自己预留字段名或者字段号,这样预留(reserved)的字段将不会被以后的用户修改了。

message Foo { reserved 2, 15, 9 to 11; reserved \”foo\”, \”bar\”;}复制代码编译器编译.proto文件

可以使用ProtocolBufer编译器将.proto文件编译成自己选择的语言。在之后可以使用编译后的消息(message)进行get/set字段值,序列化消息(message)到输出流,或者从输入流中反序列化得到消息(message)。

C++:一个.proto文件编译生成一对.h和.cc文件。Java:生成.java文件,使用消息(message)指定的Builder类创建消息(message)对象的实例。Python:Python有点不一样:会生成一个module其中包含每个消息(message)的静态描述符。Go:每一个消息(message)生成一个.pb.go文件。Ruby:生成.rb文件,是一个module中包含各个消息(message)。Objective-C:一个.proto文件生成一对pbobjc和pbobjc.m文件,每个消息(message)对应一个class。C#:生成.cs文件,每个消息(message)对应一个class。Dart:生成.pb.dart文件,每个消息(message)对应一个class。基础类型

double:float:int32:使用可变长编码,如果使用的该字段会有负数,效率将变低,这时最好使用sint32。int64:使用可变长编码,如果使用的该字段会有负数,效率将变低,这时最好使用sint64。uint32:使用可变长编码。uint64:使用可变长编码。sin32:使用可变长编码,有符号整形,有负数时使用常规的int32更有效率。sint64:使用可变长编码,有符号整形,有负数时使用常规的int64更有效率。fixed32:固定4个字节,如果数字大于2^28比uint32更有效率。fixed64:固定8个字节,如果数字大于2^56比uint64更有效率。sfixed32:固定4个字节。sfixed64:固定8个字节。bool:string:字符串,必须使用UTF-8或者7位ASCII编码格式。bytes:有任意的byte序列。 具体的proto中各个字段类型映射到对应语言中时,见下图:

默认值

如果一个消息(message)被解析了,但是其中的字段并没有被赋值,那么将会被设置为默认值。

string:空串bytes:空的bytes序列bool:false数字类型:0枚举类型:默认值为枚举类型中定义的第一个值,也就是0消息类型(message):取决于所编译的语言。 对于repeated,为空的list。枚举

这里还是以之前的查百度的例子来说,有了查询关键字query,对于结果,你有可能不只是想要浏览一下WEB页面,还行看看视频、图片、新闻啥的。那么这样定义:

syntax = \”proto3\”;message SearchRequest{ string query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Category{ option allow_alias = true; //第一个值必须为0。 UNIVERSAL = 0; WEB = 1; IMAGES = 2; NEWS = 3; PRODUCT = 4; VIDEO = 5; //启用了别名,则可以赋同一个值 GENERAL = 0; } Category result_type = 4;}复制代码使用enum关键字定义枚举类型。 枚举常量数值必须在32bit的整型中。使用负数赋值枚举常量效率低,不推荐。对于枚举常量,可以定义在消息(message)中,也可定义在消息(message)外。比如上面定义在SearchRequest中的Category,以SearchRequest.Category的方式来复用。

预留(reserved)值

同样的,对于消息(message)中可以预留(reserved)字段号,在枚举中,可以预留(reserved)值。 下面预留(reserved)了,值,名字。(2,15,9到11,40到最大值都不能后续使用)。

enum Foo { reserved 2, 15, 9 to 11, 40 to max; reserved \”FOO\”, \”BAR\”;}复制代码使用其他消息(message)作为字段

之前定义的SearchRespon消息(message):

message SearchResponse{ repeated string result = 1; int32 page_number = 2;}复制代码对于结果,我们只能获取多个字符串,让他回应的消息功能更强大一点,我们定义一个Result消息(message):

message SearchResponse{ repeated Result result = 1; int32 page_number = 2;}message Result{ string url = 1; string title = 2; repeated string snippets = 3;}复制代码在SearchRespon中,我们嵌套了一个Result消息(message),Result中有请求的地址url,标题title还有描述片段snippets。

嵌套定义

接下来,我们再具体化URL:

message SearchResponse{ repeated Result result = 1; int32 page_number = 2;}message Result{ URL url = 1; string title = 2; repeated string snippets = 3;}message URL{ enum Protocol{ ; = 1; } Protocol protocol = 1; string domain = 2; int32 port = 3; string filepath = 4;}复制代码对于SearchResponse消息(message)中返回的结果result,在Result中又有消息(message)URL,在URL中我们具体到,使用的协议、域名、端口、请求文件路径。所以,消息之间可以互相嵌套,定义更加复杂的消息。

导入

在Java中,或者其他语言,需要导入其他以及写好的包,在ProtocolBuffer中也是一样,可以导入先前定义好的.proto文件,使用其中定义的消息(message)或者服务(service)。 在同一目录下,我将写好的URL放入URL.proto文件中,在定义SearchResponse消息(message)中导入该文件:

import \”URL.proto\”;message Result{ URL url = 1; string title = 2; repeated string snippets = 3;}复制代码这样就可以复用更多的自定义消息(message)了。 对于import,只能导入其后续指定的.proto文件中定义的消息(message)或服务。比如有3个.proto文件:

/*file A.proto*/syntax = \”proto3\”;message A{}/*file B.proto*/syntax = \”proto3\”;import \”A.proto\”;message B{ A a = 1;}/*file C.proto*/syntax = \”proto3\”;import \”B.proto\”;message C{ A a = 1;}复制代码在这其中,C是看不到A的,只有在B中import public \”A.proto\”,C才能看见A。

Any字段类型

Any字段类型是Google自己对于Proto中类型的封装,并提供一定特定方法。 如下定义一个Any字段,需要导入Google提供的any.proto

在Java中使用ErrorStatus消息调用details的get方法时,返回的实例是com.google.protobuf.Any,对于该类型提供了pack和unpack方法,如下:class Any { // 对于给定的消息打包成Any类型,前缀则是默认的:type.googleapis.com public static Any pack(Message message); // 对于给定的消息打包成Any类型,前缀则是typeUrlPrefix指定的 public static Any pack(Message message, String typeUrlPrefix); // 检查该Any类型是否是给定clazz的消息类型 public <T extends Message> boolean is(class<T> clazz); // 给定clazz消息类型,将Any类型拆包成指定的消息类型,如果不匹配抛出异常 public <T extends Message> T unpack(class<T> clazz) throws InvalidProtocolBufferException;}复制代码Any字段给了一定的灵活性,在传递消息时不用指定特定的类型,可以在传递不同消息中传递不同的类型,在接收端进行判断即可。在传输时,底层还是被转换为bytes类型。

Oneof字段类型

Oneof类型如下定义。

oneof oneof_name { int32 foo_int = 4; string foo_string = 9; …}复制代码对于这个oneof消息类型,我们可以这样理解,它类似与C语言中的union类型(联合体),最后生成的Java代码是这样的:

public enum OneofNameCase implements com.google.protobuf.Internal.EnumLite { FOO_INT(4), FOO_STRING(9), … ONEOFNAME_NOT_SET(0); …};复制代码如果设置了oneof_name消息中的foo_int字段,那foo_string就无效。同样的,如果设置了foo_string字段,那么foo_int字段就无效。在Oneof类型的消息中,只有一片共享内存,每次只有一个字段被设置。 需要注意,Oneof的消息不能使用repeated描述。 在Java中提供了一下方法进行辅助使用: 对于生成类中的枚举类:

int getNumber(): 返回在.proto文件中定义的索引值,如foo_int则返回4。static OneofNameCase forNumber(int value): 返回使用索引值相应的对象,如果该对象未设置则返回null,如4则返回foo_int。 生成类中:OneofNameCase getOneofNameCase(): 返回已经设置了的对象,如果都没有被设置返回ONEOFNAME_NOT_SET。 生成类中的Builder:Builder clearOneofName(): 清空所有设置。Map字段类型

使用这样定义Map类型:

map<key_type, value_type> map_field = N;复制代码key_type:可以使用任何常规类型(int32或者string等),不能使用浮点数和bytes类型定义。value_type:可以是任何类型,除了又是一个Map。 和Oneof同样,使用Map定义的字段不可以是**repeated**的。包:package

对于.proto文件,可以使用包组织,package字段就是类似于Java中的Package。 定义的计算CalculateMsg消息,在proto.Calculation文件夹下:

其中的包就是Calculation。

最好使用package和文件夹想对应,在Java中的习惯哈。定义服务Service

在这里我使用上面CalculationMsg的消息类,定义了其相应了服务,RPC(Remote Procedure Call)。 package Calculation;

import \”Calculation/CalculatMsg.proto\”;service Calculator{ rpc Calc( CalRequest ) returns (CalResponse){}}复制代码使用Proto编译器编译上面的文件,相应于选择的语言将生成服务的接口(interface)和客户端的stub。 可以使用Google提供的gRPC,也可以使用第三方的RPC框架。 这里我给大家看看模仿grpc.io提供例子写的计算服务: 对于服务端:

复写编译生成的gRPC接口类,实现之前定义的calc函数:获取请求的需要计算方法,数值1和数值2,计算,然后放入输出流中,最后OnComplete。

客户端则先Build一个请求,阻塞调用获取结果。映射到JSON

Proto3能够转换到JSON数据格式,其相应的数据类型映射如下: 如果Proto中某个字段未设置,在JSON中就是null。

选项Option

.proto文件中可以使用option字段声明特定选项。Opion不会影响整体消息的定义,但是在特定的上下文中进行影响。 Option选项也是分级别的,有时候在外定义,则影响的是文件级别,如:java_package、java_multiple_files、java_outer_classname等,分别是:编译后在哪个java包下,是否将.proto文件中不同消息分成多个文件,定义编译后的java类名。

之前定义的计算服务就是如上,生成的package在tech.sylardaemon.Calculation中,生成后的类名CalculatorProt,java_generic_services为true则是生成gRPC相应的服务接口和客户stub。 还可以自己定义option,是ProtoBuf的一种高级应用,这里就略过了,有兴趣的同学可以自己查查看。编译器使用

编译器的使用如下:

protoc –proto_path = IMPORT_PATH –Language_out = DST_DIR path/to/*.proto复制代码–proto_path:该参数输入的IMPORT_PATH是指定你要编译的***.proto文件中import指令中查找的目录。如果省略,则使用当前编译器执行的目录。也可以多次使用–proto_path指定多个导入目录。可以使用-I**缩短。–Language_out:可以提供一个或多个输出目录:–cpp_out:生成C ++代码的目的目录–java_out:生成Java代码的目的目录–python_out:生成Python代码的目的目录–go_out:生成Go代码的目的目录–ruby_out:生成Ruby代码的目的目录–objc_out:生成Objective-C代码的目的目录–csharp_out:生成C#代码的目的目录–php_out:生成PHP代码的目的目录path/to/*.proto:最后的则是将要被编译的proto文件路径。完

作者:LudwigWuuu

链接:://juejin.im/post/5c0a82ed6fb9a049ad76dd56

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/cjia/6131.html

单片机控制交流电机正反转的C语言程序,暂停怎么办

怎样打造万能Windows封装包?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部