Protobuf 入门
IDEA中的Protobuf的插件的使用
- 首先File->setting中找到plugins
- 然后安装插件
- 安装完成后重启IDEA就可以了
- 测试: 在src/main/下建立proto文件夹,并在其中建立以proto为后缀文件
-
然后将如下的proto协议写进去
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
-
然后maven中添加
<properties> <grpc.version>1.6.1</grpc.version> <protobuf.version>3.3.0</protobuf.version> </properties> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.0</version> <configuration> <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
- 等待下载完成后,然后点击maven中的compile插件,运行完,如果配置正确,在项目的target的目录下就生成了我们需要的类文件了
- 好了介绍到这里,IDEA中使用protobuf插件就完成了,下面是关于protobuf的简介和一些详细内容
简介
- Google Protocol Buffer 简称Protobuf是Google公司内部的开源项目,可用于RPC系统和持续数据存储系统
- 是一种轻便高效的结构化数据存储格式,可用于结构化数据串行化,或者序列化,非常适合数据存储或RPC数据交换格式,可用于通讯协议,数据存储等领域,并且是平台无关的
定义数据类型
-
如上在IDEA中运行的protobuf文件,我们拿这个来说
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
-
syntax
指定protobuf的版本,必须是文件的第一行 -
message
代表这是一个消息,SearchRequest
消息中指定了三个字段,每一个字段对应于要包含在这种类型的消息中的数据,每一个字段都有一个名称和类型,比如上面的int32 page_number = 2;
,名称就是page_number
,而类型就是int32
,注意等号后面的不是默认值,而可以认为他是此page_number
字段的身份证,不可重复,这些数字用于以二进制格式标识您的字段,一旦消息类型被使用就不应该被更改,需要注意的是1到15范围内的字段编号需要一个字节来编码,而16到2047范围内的字段编号需要两个字节,所以经常使用的应该为其编码为1到15之间,这些数字的范围是[1, 536870911]
,其中19000~19999
是为协议缓冲区实现而保留的,不可以使用,否则报错
-
标量值类型
- 即
int32 page_number = 2;
中的int32
叫做标量值类型 - 下面是标量值类型以及生成的java中的对应类型
类型 | 说明 | 对应类型 |
---|---|---|
double | null | double |
float | null | float |
int32 | 使用可变长度编码,编码负数效率低下,如果你的字段可能有负值,请改用sint32 | int |
int64 | 使用可变长度编码,编码负数效率低下,如果你的字段可能有负值,请改用sint64 | long |
uint32 | 使用可变长度编码 | int |
uint64 | 使用可变长度编码 | long |
sint32 | 使用可变长度编码,符号整型值,这些比常规int32编码负数更有效 | int |
sint64 | 使用可变长度编码,有符号的int值,这些比常规int64编码负数更有效 | long |
fixed32 | 总是四字节,如果值经常大于2^28,则比uint32更有效 | int |
fixed64 | 总是八字节,如果值经常大于2^56,则比uint64更有效 | long |
sfixed32 | 总是四字节 | int |
sfixed64 | 总是八字节 | long |
bool | null | boolean |
string | 字符串必须始终包含UTF8编码或7位ASCII文本 | string |
bytes | 可以包含任意字节序列 | byteString |
- 如果你使用的是别的语言,那么还有一张对照图,在下面
修饰符
- 如果一个字段被repeated修饰,则表示它是一个列表类型的字段
-
如果你现在不确定是否某个值或者变量是否以后会使用的到为了不误写,那么可以保留
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; string foo = 3 // 编译报错,因为‘foo’已经被标为保留字段 }
缺省值:默认值
- string类型的默认值是空字符串
- bytes类型的默认值是空字节
- bool类型的默认值是false
- 数字类型的默认值是0
- enum类型的默认值是第一个定义的枚举值
- message类型(对象,如上文的SearchRequest就是message类型)的默认值与 语言 相关
- repeated修饰的字段默认值是空列表,比如
repeated string query = 1; = java.util.List<java.lang.String>
- 如果一个字段的值等于默认值(如bool类型的字段设为false),那么它将不会被序列化,这样的设计是为了节省流量
枚举
- 之前的都是简单类型,比如
int32
之类的,那么在message中还可以定义复杂类型 - 每个枚举值有对应的数值,数值不一定是连续的.第一个枚举值的 数值必须是0且至少有一个枚举值 ,否则编译报错.编译后编译器会为你生成对应语言的枚举类,零值必须为第一个元素,以便与proto 2语义兼容,其中第一个枚举值总是默认的
- 由于编码原因,出于效率考虑,不推荐使用负数作为枚举值的数值
-
如下就在之前的基础上添加到了春夏秋冬四个枚举常量值,当然里面的数字标识不用按顺序来
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; Groups groups = 4; enum Groups{ SPRING = 0; SUMMER = 1; Autumn = 2; WINTER = 3; } }
-
如果个别的枚举值的标识是相同的,需要用
option allow_alias = true;
来声明一下,比如enum Groups{ option allow_alias = true; SPRING = 0; SUMMER = 2; Autumn = 2; WINTER = 3; }
- 如果去掉
option allow_alias = true;
,那么编译会出现错误, 并且注意枚举数常量必须在32位整数的范围内 -
可以使用
messageName.enum
的形式,在其他message中引用,比如message Others { SearchRequest.Groups groups = 1; }
- 其生成的类中方法定义的返回值就是
Mes.SearchRequest.Groups getGroups();
使用其它的message类型
-
第一种使用方法如下
syntax = "proto3"; message First { repeated string query = 1; Second second = 2; //引用其他message类型 } message Second { int32 num = 1; }
-
第二种即import导入使用
syntax = "proto3"; import "second.proto"; //首先导入 message First { repeated string query = 1; Second second = 2; //引用其他message类型 }
- 上面这种导入如果测试,需要将
second.proto
移到相同目录下,并且First和Second消息是两个proto文件
- 上面这种导入如果测试,需要将
嵌套
-
可以在一个message类型中定义另一个message类型,并且可以一直嵌套下去,类似Java的内部类
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
-
可以使用
Parent.Type
的形式引用嵌套的messagemessage SomeOtherMessage { SearchResponse.Result result = 1; }
更新message
-
当proto不能满足要求的情况下,那么我们就需要按照要求更新message
- 不要更改任何已有的字段的数值标识
- 如果增加新的字段,使用旧格式的字段仍然可以被新产生的代码所解析.相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉.注意:未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的
- 非required的字段可以移除,只要它们的标识号在新的消息类型中不再使用,但是更好的做法是将此字段重命名,保证其标识号不被其他变量误用
- nt32, uint32, int64, uint64,和bool是全部兼容的,如果转换的时候发生不符合的情况,那么会强制类型转换
- sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容
- string和bytes是兼容的,只要bytes是有效的UTF-8编码
- 嵌套消息与bytes是兼容的,只要bytes包含该消息的一个编码过的版本
- fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的
- 枚举类型与int32,uint32,int64和uint64相兼容,,如果转换的时候发生不符合的情况,那么会强制类型转换
Any
-
Any允许包装任意的message类型 ,一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型.为了使用Any类型,你需要导入
import google/protobuf/any.proto
import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; }
- 如上会被解析成
private java.util.List<com.google.protobuf.Any> details_;
类型 - 对于给定的消息类型的默认类型URL是
type.googleapis.com/packagename.messagename
- 不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值,例如在java中,Any类型会有特殊的
pack()
和unpack()
访问器 -
实例
//People.proto syntax = "proto3"; message Get{ string name = 1; } //First.proto syntax = "proto3"; import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; }
public class Tests { public static void main(String[] args) throws InvalidProtocolBufferException { People.Get wangziqiang = People.Get.newBuilder().setName("wangziqiang").build(); First.Response build = First.Response.newBuilder().setData(Any.pack(wangziqiang)).build(); System.out.println(build.getData()); System.out.println(build.getData().unpack(People.Get.class).getName()); } /** * 输出结果 * type_url: "type.googleapis.com/Get" * value: "\n\vwangziqiang" * wangziqiang */ }
- 如上我们就看到了,Any可以包含任何的message,也就是进行pack动作,而unpack是进行解包得到内容,这是我自己的理解
Oneof
-
用于几个参数只能初始化其中一个的情况,设置其中一个字段会清除其它字段,repeated不可以在其中被使用
syntax = "proto3"; message Name{ oneof set_name{ string first_name = 1; string last_name = 2; } }
public class Tests { public static void main(String[] args) { People.Name wangziqiang = People.Name.newBuilder().setFirstName("nn").setLastName("wangziqiang").build(); System.out.println("getFirstName -> " + wangziqiang.getFirstName()); System.out.println("getLastName -> " + wangziqiang.getLastName()); System.out.println(wangziqiang.getFirstName().isEmpty()); System.out.println(wangziqiang.getLastName().isEmpty()); } /** * getFirstName -> wangziqiang * getLastName -> * true * false * 也就可以看出来,只有最后一次设置的字段有值. */ }
Map
-
其实是一种语法糖,如下
message xx{ int32 i = 1; string j = 2; } message ss { xx x = 1; //map<int32,string> = 1 }
-
需要注意的是key除了floating和bytes的任意标量类型都是可以的,value可以是任意类型
- map的顺序不一定
- map类型字段不支持repeated
- 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序
- 如果有重复的key则后一个key不会被使用
-
实例
syntax = "proto3"; message Name{ map<int32,string> names = 1; }
public class Tests { public static void main(String[] args) throws InvalidProtocolBufferException { People.Name.Builder builder = People.Name.newBuilder(); builder.putNames(1,"one"); builder.putNames(3,"three"); builder.putNames(2,"two"); People.Name name = builder.build(); System.out.println(name); } /** * names { * key: 1 * value: "one" * } * names { * key: 3 * value: "three" * } * names { * key: 2 * value: "two" * } */ }
包
-
使用方法
syntax = "proto3"; package qidai; message Name{ map<int32,string> names = 1; }
定义服务
-
如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码
syntax = "proto3"; package qidai; message NameRequest{ string name = 1; } message NameResponse{ int32 name_hash = 1; } service rpc_name { rpc getName(NameRequest) returns(NameResponse); }
- 上面的表示,rpc的参数为NameRequest,而返回值为NameResponse,对于java应用protobuf实现简单的rpc,之后再说
JSON映射
- 在protobuf中支持使用JSON,那么对于protobuf中的类型和JSON中的类型的对应表如下
proto3 | JSON | JSON示例 | 注意 |
---|---|---|---|
message | object | {"fBar": v, "g": null, …} | 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 |
enum | string | "FOO_BAR" | 枚举值的名字在proto文件中被指定 |
map | object | {"k": v, …} | 所有的键都被转换成string |
repeated V | array | [v, …] | null被视为空列表 |
bool | true, false | true, false | |
string | string | "Hello World!" | |
bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" | |
int32,fixed32,uint32 | number | 1, -10, 0 | JSON值会是一个十进制数,数值型或者string类型都会接受 |
int64,fixed64,uint64 | string | "1", "-10" | JSON值会是一个十进制数,数值型或者string类型都会接受 |
float,double | number | 1.1, -10.0, 0, "NaN", "Infinity" | JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 |
Any | object | {"@type": "url", "f": v, … } | 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{"@type": xxx, "value": yyy}否则,该值会被转换成一个JSON对象,@type字段会被插入所指定的确定的值 |
Timestamp | string | "1972-01-01T10:00:20.021Z" | 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数 |
Duration | string | "1.000340012s", "1s" | 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 |
Struct | object | { … } | 任意的JSON对象,见struct.proto |
Wrapper types | various types | 2, "2", "foo", true, "true", null, 0, ... | 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null |
FieldMask | string | "f.fooBar,h" | 见fieldmask.proto |
ListValue | array | [foo, bar, …] | |
Value | value | 任意JSON值 | |
NullValue | null | JSON null |
- 下面也是对照表
选项
- 在定义.proto文件时能够标注一系列的options.Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式.完整的可用选项可以在google/protobuf/descriptor.proto找到,在IDEA中直接搜索
descriptor.proto
就行 - 一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中.一些选项是消息级别的,意味着它可以用在消息定义的内部.当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中
-
常用的选择
-
java_package
:文件选项,即指定包名,如下,但是自己感觉好像与package com.qidai;
一样的作用syntax = "proto3"; option java_package = "com.qidai"; message NameRequest{ string name = 1; }
-
java_outer_classname
:文件选项,即指定外部类类名,如果不指定,那么默认的类名是以proto文件的名字作为类名的syntax = "proto3"; option java_outer_classname = "Peoples"; //然后编译完成后就是生成的Peoples的类名,而不是以proto文件名作为类名了 message NameRequest{ string name = 1; }
-
optimize_for
:文件选项,可以被设置为SPEED
,CODE_SIZE
,或者LITE_RUNTIME
,这些值将影响代码的生成-
SPEED
:默认protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作.这种代码是最优的. -
CODE_SIZE
:protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作.采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些,但是生成的代码的访问方式和SPEED
是一样的 -
LITE_RUNTIME
protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf).这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多.这种模式经常在移动手机平台应用多一些
syntax = "proto3"; option optimize_for = SPEED; message NameRequest{ string name = 1; }
-
-
deprecated
:表示废弃了syntax = "proto3"; message NameRequest{ string name = 1 [deprecated = true]; }
-
生成访问类
-
如上我们都是在IDEA插件的帮助下,自动完成编译的,自己接触的是Java,所以在这里列一下Java的生成类的方式,如果你是别的语言,那么请参考给出的参考博文
protoc --proto_path=IMPORT_PATH --java_out=DST_DIR path/to/file.proto
-
IMPORT_PATH
声明了一个.proto文件所在的解析import具体目录,如果忽略该值,则使用当前目录,如果有多个目录则可以多次调用--proto_path
,-I=IMPORT_PATH
是--proto_path
的简化形式 -
--java_out
在目标目录DST_DIR中产生Java代码 - 作为一个方便的拓展,如果DST_DIR以.zip或者.jar结尾,编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中.注意如果输出已经存在则会被覆盖
-
- 好了到这就是Protobuf的基本用法,本文参考网上的博文,链接如下,如果本文有什么错误请及时指出 , 谢谢咯
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Leetcode 142. Linked List Cycle II
地址:Leetcode 142. linked list Cycle II 问题描述:检测链表是否存在环,是的话返回环入口,否则返回None. 这道题有两个思路,一个是经典的快慢指针的思路,另外一个是利用hashMap来记住已经访问过的Node。 思路一:检测到环的时候,令慢指针指向head,然后fast 和 slow指针皆一次移动一个,到他们再次相遇的时候就是环的入口。简单证明如下,来自Leetcode网友: 代码: class Solution(object): def detectCycle(self, head): """ :type head: ListNode :rtype: ListNode """ if not head or not head.next: return None slow,fast = head,head while(fast and fast.next): slow = slow.next fast = fast.next.next if (slow == fast): slow = head while(slow!=fast): slow = sl...
- 下一篇
原创C#多数据库组件(SQL+Oracle+MySQL)及用户操作手册|CSFramework.DB.dll
我们在开发软件过程中,特别是基于数据管理应用相关的系统,一般采用一种数据库,如Microsoft SQL Server,超大型系统有的使用Oracle, 部分Web系统采用开源的MySQL等,由于各种业务场景以及用户需求,促使我们的数据库系统部署在不同类型的数据库服务器上。 若开发的系统能支持多种数据库的快速切换,可以为我们减少很多烦恼,同时提高系统的适应性、兼容性以及可扩展性。 C/S框架网为广大用户原创定制基于C#语言的多数据库组件包及用户操作手册。 CSFramework.DB.realease.rar 链接: https://pan.baidu.com/s/1bmzNVwQ4-F2svTLByIPaQA 密码:fy9s 《CSFramework.DB用户操作手册》.pdf 链接: https://pan.baidu.com/s/1rauYthmGjHQ5T8IeLuG0Ww 密码:1fsu 如何使用多数据库IDatabase接口以及数据访问层应用 IDatabase接口应用 – 获取实体对象 IDatabase接口应用 – 返回DataTable 数据访问层应用 数据访问...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker快速安装Oracle11G,搭建oracle11g学习环境