简介
服务端
Grpc.AspNetCore
客户端
Google.Protobuf Protobuf 序列化协议的包
Grpc.Net.Client 客户端的包
Grpc.Net.ClientFactory HTTPClientFactory集成的包
Grpc.Tools 命令行工具
.proto文件
定义包、库名
定义服务 “service”
定义输入输出模型 “message”
异常
使用 Grpc.Core.RpcException
使用 Grpc.Core.Interceptors.Interceptor
HTTPS证书
使用自制证书
使用非加密 HTTP2
Protocol Buffer
.proto文件
.proto文件包括两部分:
- gRPC服务的定义
- 服务端和客户端传的消息
标量类型
数值型
,数值型有很多种形式: double, float, int32, int64, uint32, uint64, sint32,sint64,fixed32,fixed64, sfixed32, sfixed64。
根据需要选择对应的数值类型。
布尔型
, bool型可以有True和False两个值。
字符串
,string表示任意长度的文本,但是它必须包含的是UTF-8编码或7位ASCII的文本,长度不可超过232。
字节型
,bytes可表示任意的byte数组序列,但是长度也不可以超过232,最后是由你来决定如何解释这些bytes。例如你可以使用这个类型来表示一个图片。
字段的数值(Tag)
在Protocol Buffers里面,字段的名其实没那么重要,但是写C#代码的时候,字段名还是很重要的。
对于protobuf来说,这个tag
是更为重要的。
可以使用的最小的tag数值是1,最大值是229-1,或者536,870,911。但是你不可以使用19000到19999之间的数,这部分数是保留的。
还有一点值得注意的是:
从1到15
的Tag数只占用1个字节的空间,所以它们应该被用在频繁使用的字段上。而从16到2047
,则占用两个字节,它们可以用在不频繁使用的字段上。
字段规则
protobuf 的字段必须满足以下两个规则之一:
- 单数字段(Singular)
大概意思就是指这个字段只能出现0或1次(不能超过一次),这也是proto3的默认字段规则。 - 重复字段(Repeated)
与singular相对的就是repeated。如果你想做一个list或数组的话,你可以使用重复字段这个概念。这个list可以有任何数量(包括0)的元素。它里面的值的顺序将会得到保留
保留的字段
如果你对你定义的消息类型进行了更新,例如删除某个字段或者注释掉某个字段,那么其它开发者在以后更新这个消息类型的时候可能会重新使用被你删除/注释掉的字段的数值(tag)。如果以后还需要使用这个消息类型的老版本的proto文件,那么这将会引起严重的问题,例如数据损坏、隐私漏洞等等。
那么一种避免此类事情发生的解决办法就是将你删除/注释掉的这些字段的数值(或/并且包括字段名,因为字段名可引起JSON序列化的问题)标记为reserved
,如果其他人再使用这个数值作为字段标识符,那么编译器就会有错误提示
message Person {
int32 id = 1;
string name = 2;
float height = 3;
float weight = 4;
bytes avatar = 5;
string email = 6;
bool email_verified = 7;
repeated string phone_numbers = 8; // packed
reserved 9,10,20 to 100,200 to max; //保留9,10,20-100,200以上
reserved "foo","bar"; //保留字段名
}
字段的默认值
当消息被解析的时候,如果编码的消息里不含有特定的一个singular元素,那么在被解析对象里相应的字段就会被设为默认值。
常用类型的默认值如下:
- string:
空
字符串 - bytes:
空
的byte数组。 - bool:
false
- 数值型:
0
- 枚举enum:枚举里定义的第一个枚举值,值必须是
0
- repeated:通常是相应开发语言里的
空
list - 还有个消息类型的字段,它的默认值和开发语言有关。
默认值在更新Protocol Buffer消息定义的时候有很重要的作用,它可以防止对现有代码/新代码造成破坏性影响。它们也可以保证字段永远不会有
null
值。但是,默认值还是非常危险的:
你无法区分这个默认值到底是来自一个丢失的字段还是字段的实际值正好等于默认值。需要保证这个默认值对于业务来说是一个毫无意义的值。例如 Int32 pop (人口)默认值就可以设置为
-1
。
再就是,可能需要在你的代码里来做一些对默认值的判断,从而进行处理。
枚举
枚举里面的常量的值必须不能超过32位整型的数值,不建议使用负数。
枚举可以定义在 message
里面,也可以在外边单独定义以便复用。如果另一个消息想使用 Person
里面这个 Gender
枚举,那么可以使用 Person.Gender
这种形式。
但是如果代码不知道它接收到的值对应哪个enum值,那么enum的默认值将会被采用。默认值 = 0。
为枚举值起别名
枚举值是可以起别名的,起别名的作用就是允许两个枚举值拥有同一个数值。
要想起别名,首先需要设置 allow_alias
这个 option
为 true
。
然后我们为 FEMALE
这个枚举值起了一个别名叫做 WOMAN
,它们的数值是一样的。同样的 MAN 是 MALE 的数值也是一样的。
enum Gender {
option allow_alias = true;
NOT_SPECIFIED = 0;
FEMALE = 1;
MALE = 2;
WOMAN = 1;
MAN = 2;
}
import 引用
引用其他文件的 massage
的方法
import "date.Persion"
Packege 命名空间
如果出现两个文件的 message
都叫 Person
,避免冲突
package "my.project"
option csharp_namspace = "My.WebApis" //C#生成指定的命名空间
设置Protocol Buffers编译器
protoc编译器主要就是用来生成代码的,它的下载地址目前是:
https://github.com/protocolbuffers/protobuf/releases/
下载相应的编辑器,这里使用protoc-3.17.3-win64.zip,先设置WINDOWS环境变量,在使用CMD命令
--csharp_out=OUT_DIR @<filename> 生成文件夹
-IPATH, --proto_path=PATH 指定了要去哪个目录中搜索import中导入的和要编译为.cs的proto文件,可以定义多个,
D:Projectspb>protoc --csharp_out=csharp *.proto
更新消息类型的规则
-
不要修改任何现有字段的数字(tag)
你可以添加新的字段,那些使用旧的消息格式的代码仍然可以将消息序列化,您应该注意这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。类似的,新代码所创建的消息也可以被旧代码解析:旧的二进制在解析的时候会忽略新的字段。 -
字段可以被删除,只要它们的数字(tag)在更新后的消息类型中不再使用即可。你也可以把字段名改为使用“OBSOLETE_"前缀而不是删除字段,或者把这些字段的数字(tag)进行保留( reserved),以免未来其它开发者不消息使用了删除字段的数字。
-
对于数据类型的变化,例如
int32
到int64
,string
到bytes
等等,可以参考官方文档:
https://developers.google.com/protocol-buffers/docs/proto3#updating。但是建议还是尽量不要去修改字段的数据类型。
message Person {
int32 id = 1;
string name = 2;
float height = 3;
float weight = 4;
bytes avatar = 5;
string email = 6;
bool email_verified = 7;
repeated string phone_numbers = 8;
//(1)删除字段,要reserved 9,reserved "foo"
//string foo = 9;
reserved 9;
reserved "foo"; //保留字段名
//(2)废弃字段,加前缀OBSELETE_,就不需要
string OBSELETE_bar = 10;
}
gRPC原理
生命周期
身份认证
这里指的不是用户的身份认证,而是指多个server和client之间,它们如何识别出来谁是谁,并且能安全的进行消息传输。
在身份认证这方面,gRPC一共有4种身份认证的机制:
- 不采取任何措施的连接,也就是不安全的连接。
- TLS/SSL连接。
- 基于Google Token的身份认证。
- 自定义的身份认证提供商。
消息传输类型
gRPC的消息传输类型有4种:
- 第一种是一元的消息,就是简单的请求--响应。
- 第二种是server streaming(流),server会把数据streaming回给client。
- 第三种是client streaming,也就是client会把数据streaming给server。
- 最后是双向streaming。