协议缓存区(Protocol Buffers)
直观的一个表现形式就是.proto扩展名的普通文本文件,在这个文件中可以定义消息、gRPC服务。一般来讲,服务和消息搭配使用。消息相当于 我们的类,可以做为方法的参数和返回值。
协议缓存区数据被构造为消息
// 消息
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
如下,.proto文件。定义了一个服务和两个消息
// The greeter service definition.
service Greeter {
// Sends a greeting PascalCase
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
接下来,我们先了解一下,如何正确的定义一个消息
// cat.proto
package cat.bar;
message Cat{
string name = 1;
int32 age = 2;
}
helloworld.proto文件
syntax = "proto3"; // 指定使用proto3语法 ,不指定默认使用proto2语法。必须为文件有效的第一行
//导入另一.proto文件,以便在该3文件中使用cat.proto中定义的消息
import "cat.proto";
// .proto文件的包名,未指定java_package时,默认使用该包名作为生成java类的包名
package com.wanfangdata.titan;
// 生成后的java类的包名
option java_package = "com.wanfangdata.titan.vsearch"
// 将定义的服务和消息生成在多个文件中
option java_multiple_files = true;
// 要生成的最外层java类(以及文件名)的类名。如果没有显示的指定这个option,则是会通过.proto的文件名转成大驼峰加上OutClass作为类名(如:HelloWorldOuterClass.java)
option java_outer_classname = "HelloWorldProto";
// 定义一个消息
message Person {
string name = 1; // 字段名称和类型大家应该都知道,唯独对这个数字比较陌生。
int32 id = 2;
bool isHasHobby = 3;
enum Corpus { // 枚举可以写在消息中,也可以引用其他消息中的枚举,也可以写在消息外边
UNIVERSAL = 0; // 标识符必须从0开始,默认值会取第一个值
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4; // 上边定义了枚举,在这里引用使用
repeated Student student = 5; // 使用其他的Message类型
Cat cat = 6; // 引用定义的其他消息类型
//cat.bar.Cat cat = 6; 直接连package的包加上,可以防止多个.proto文件中都含有消息Cat冲突
message Son { // 可以再消息中直接定义一新的消息,然后直接引用
string name = 1;
int32 age = 2;
}
repeated Son son = 6;
// oneof 可以有多个可选的字段,只能设置其中一个字段,如果设置多个字段,只有最后一个生效,其他的都会被清除
oneof test_oneof{
string name = 1;
int32 age = 2;
}
//Maps类型
//其中 key_type 可以是任何整数或字符串类型(任何标量类型除浮点类型和 bytes)。
//请注意,枚举不是有效的 key_type。value_type 可以是除 map 之外的任何类型。
map<key_type, value_type> map_field = N;
}
message Student{
string name = 1;
int32 age = 2;
Person.Son son = 3; // 引用了person消息的子消息son
}
//一个.proto文件中可以定义多个消息,一般来讲,是定义多个相关的消息,比如请求的类型和响应的类型
分配标识号
我们看到在上边的定义消息中,给每一个字段都定义了一个唯一的数字值。这些数字是用来在消息的二进制格式中识别各个字段的,一旦开始使用,就不能再改变。
注意:
- 【1,15】范围内的标识号在编码的时候会占用一个字节。
- 【16,2047】范围的标识号在编码的时候则占用2个字节。
所以应该为那些频繁出现的消息元素,保留【1,15】范围的标识号。切记,要为讲来有可能添加的、频繁出现的字段预留一些标识号。 - 最小的标识号可以从1开始,最大到2^29-1。不可以使用其中的【19000,19999】的标识号,protobuf协议实现中对这些进行了预留。
如果非要使用,在使用protoc进行编译的时候,会报如下ptotoc did exit cleanly.失败的警告。
Failed to execute goal org.xolstice.maven.plugins:protobuf-maven-plugin:0.6.1:compile-cpp (default-cli) on project grpc-java-springboot-demo: protoc did not exit cleanly. Review output for more information.
指定字段规则
消息的字段可以是以下情况之一:
- singular (默认):一个格式良好的消息可以包含该字段可以出现0或者1次。
- repeated:在一个格式良好的消息汇总,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。
添加注释:
这个和平时用的一样,单行和多行注释都可以 // 或者 /* */
从.proto文件生成了什么?
对于java来说,编译器会为每个消息类型生成一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
对于C#语言,编译器会为每一个.proto文件创建一个.cs文件,为每一个消息类型都创建一个类来操作。
标量类型
一个标量消息字段可以含有一个如下的类型---- 该表格展示了定义于.proto文件中的类型,以及与之对应的,在自动生成的访问类中定义的类型。
默认值
在.proto文件中可以设置默认中吗,在proto2语法中可以,但是在proto3语法中不支持。字段的默认值,相应的字段在被解析的对象中会被设置默认值。这些默认值是和类型有关系的。
- 字符串默认值为空字符串
- 字节类型默认值为空字节。
- 布尔类型默认值为false
- 数值类型默认值为0
- 枚举类型默认值是其定义中的第一个值,它必须为0
- 消息类型的默认值没有设置。它的具体值与使用的编程语言有关。
- repeated字段的默认值为空(通常是相应编程语言的空列表)
定义服务
我们的服务接口,也是定义在.proto文件中的,和消息定义在一起。
JSON映射
proto3支持标准的JSON编码,使得在不同的系统直接共享数据 变得简单。下表列出的是基础的类型对照。
在json编码中,如果某个值被设置为null或者丢失,在映射为protobuf的时候会转换为相应的默认值。 在protobuf中如果一个字段是默认值,在映射为json编码的时候,这个默认值会被忽略以节省空间。可以通过选项设置,使得json编码输出中字段带有默认值。
grpcurl使用
- 没有请求参数:
# no TLS
grpcurl -plaintext grpc.server.com:80 my.custom.server.Service/Method
- 有请求参数
使用-d添加参数,参数必须在服务地址和方法名前边
grpcurl -d '{"id": 1234, "tags": ["foo","bar"]}' \
grpc.server.com:443 my.custom.server.Service/Method
- 列出所有的服务
grpcurl localhost:50051 list 列出该连接下的所有的服务
// grpc.reflection.v1alpha.ServerReflection
//helloworld.Greeter
列出服务下的所有方法
grpcurl localhost:50051 list helloworld.Greeter
// helloworld.Greeter.SayHello
如果还想了解方法的细节,可以使用grpcurl提供的describe子命令查看更详细的描述信息:
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
在获取到方法的参数和返回值类型之后,还可以继续查看类型的信息。下面是用describe命令查看参数 .helloworld.HelloRequest类型的信息:
grpcurl -plaintext localhost:50051 describe .helloworld.HelloRequest
注意:
该命令需要开启反射服务,否则的话会提示如下错误
开启反射服务:
- 导入依赖
- 修改服务端,将反射服务加入
当没有配置这个公钥和私钥时,会报如下错误。grpcurl默认需要走tls校验的,可以通过-cert
和-key
参数设置公钥和私钥文件
对于没有没用tls协议
的grpc服务,通过-plaintext
参数忽略tls证书的验证过程。如果是Unix Socket协议,则需要指定-unix
参数。
- 请求服务接口数据
grpcurl -d '{"id": 1234, "tags": ["foo","bar"]}' \
grpc.server.com:443 my.custom.server.Service/Method
注意:
在window下输入的json串,外层使用双引号,或者直接不写,单引号报错
在window下输入json串,json的字段需要进行转义,如果不进行转义,会报错。