作者:莫乂乂_465 | 来源:互联网 | 2023-05-17 16:38
gRPC是Google基于HTTP2和protobuf推出的一款也是当下热门的开源RPC(RemoteProcedureCall)框架。可在程序或者服务之间进行高性能低带宽的通信,
gRPC是Google基于HTTP/2和protobuf推出的一款也是当下热门的开源RPC(Remote Procedure Call)框架。可在程序或者服务之间进行高性能低带宽的通信,并且支持身份认证、日志系统等等需要用到的功能。在微服务作为主流的时代,各个服务之间的通信也是一个亟需解决的问题。在ASP.NET Core 3.x下,gRPC也是微软传统RPC框架WCF的有效替代。
使用gRPC,可以让客户端像调用本地方法一样地去调用服务端中的方法。gRPC是一种合约优先的API开发模式,就是我们需要先具体地定义好方法和参数后,再进行服务端功能开发和客户端调用。并且客户端和服务端可以是使用不同语言开发的程序,通过gRPC,一旦我们在自己的服务中定义了proto文件,任何其他gRPC支持的语言开发的程序都可以来调用这个通信,通信中涉及到的环境、序列化等gRPC都帮我们完成了。默认情况下,gRPC是使用Protocol Buffers作为其接口定义语言(Interface Definition Language),就是用来定义通信中要用的方法和参数。Protocol Buffers不依赖特定的语言,根据不同需求,编译器就可以将其转换生成C#、Java、Python、Go等十几种语言供我们开发使用,并且在通信中数据是序列化成二进制流的,从而获得更好的传输性能。
本文接下来简单介绍Protocol Buffers和gRPC在.NET Core中的基本用法,主要参考为官方文档和各位大佬的教程(文末有链接)。本文Demo已上传至☞GitHub。
那么先来简单介绍一下Protocol Buffers的语法。
○ 文件名后缀用 ".proto"
○ 别忘记在文首加上一句“syntax = "proto3",来指明使用的是proto3的语法(因为之前还有一个proto2)
○ 通过在proto文件中定义message类型来指明你想序列化传输的对象,可以类比成一个类其中包含你需要的多个字段。比如定义一个叫Person的message,其中包含3个字段。
1 message Person {
2 string name = 1;
3 int32 id = 2;
4 bool has_pOnycopter= 3;
5 }
○ 简单介绍下Protocol Buffer中常用的数据类型
§ 数值:double, float, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64
§ 字符串:string
§ 布尔:bool
§ 字节:bytes ,最大长度232
§ 枚举:enum
□ 定义枚举的方法,另外支持使用别名,别名是用不同的名称表示同一个枚举值,如下面的EnumAllowingAlias.STARTED和EnumAllowingAlias.RUNNING表示的是同一个枚举值。
1 enum EnumAllowingAlias {
2 option allow_alias = true; //表示可以使用别名
3 UNKNOWN = 0; //枚举的编号是从0开始的
4 STARTED = 1;
5 RUNNING = 1;
6 }
○ 定义message时,注意到每个字段都加了一个“唯一标识”的编号,这些编号用来在message转成二进制形式后标识具体字段是啥,在投入使用后尽可能避免修改字段的编号。编号范围是1~229-1其中编号1~15的字段使用一个字节来编码,编号16~2047则使用两个字节。所以将使用频率高的字段用1~15来编号,另外19000~19999是Protocol Buffers的保留字段,最好别用。
○ 字段的规则分为两种,singular(proto3中默认)和repeated,定义字段时二选一
§ singular
大概是表示为单值,这个字段的值最多一个,与repeated相对
§ repeated
大概像是集合类型,比如定义一个字段如“repeated string emails”大概意思可理解为List emails
○ 注释的写法和C#文件中注释的写法基本一致,用“//”或者“/* … */”
○ 保留字段。如果一个已投入使用的proto中移除了某些字段的话,而用户仍然使用这些字段编号就会造成一些较严重的错误。解决办法是将这些想移除的字段名或者字段编号使用reserved关键字修饰。使用了reserved标注的字段在未来使用时,protocol buffer编译器将会抛出错误。
1 message Foo {
2 reserved 2, 15, 9 to 11;
3 reserved "foo", "bar";
4 }
○ 当使用protocol buffer编译器将proto文件编译成C#语言,会自动生成.cs文件,其中为每个message编译成class。
○ 编译后的类型与C#中类型的对应关系
proto类型 |
double |
float |
int32 |
int64 |
string |
bool |
bytes |
enum |
C#类型 |
double |
float |
int |
long |
string |
bool |
ByteString |
enum |
默认值 |
0 |
0 |
0 |
0 |
string.Empty |
false |
空字节数组 |
枚举中的第一个值 |
○ 定义完message后,可以将其打包供其他service或者message引用,那么在protocol buffer中打包和引用的方法也很简单
§ 打包语法:
package foo.bar;
message Open { ... }
§ 指定生成自定义的C#命名空间的语法:
option csharp_namespace = "Foo.MyBar";
§ 引用其他proto文件中定义的message类型的语法:
import "myproject/other_protos.proto";
○ 有了message的定义,要在RPC中应用的话就需要定义“方法”,在protocol buffer中即是service。在.proto文件中定义service的语法是:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
○ 其中service和rpc都是关键字,Search是“方法”名,SearchRequest与SearchResponse都是定义的message类型。
○ 定义好service后,proto编译器就会将service使用我们选择的语言编译成的服务接口的代码。
○ Protocol buffers的一些其他关键字如any,oneof等暂时没用上,就先不列出了,可参考官方文档。
gRPC Demo实践
IDE使用的VS2019,首先使用ASP.NET Core建立一个gRPC的服务端,使用一个WPF程序作为客户端实现最基本的gRPC通信Demo,以一个员工信息的增查为例来演示gRPC中的常用场景。
gRPC 通常有四种模式的通信,分别是“一元(unary)”,“客户端流(client streaming)”,“服务端流(server streaming)” 以及“双向流模式( bidirectional streaming)”,对于 HTTP 2 来说其实都是用流的模式,以下实验是参考杨旭大佬的教程。
首先创建gRPC服务端,新建一个空白的ASP.NET Core Web应用程序命名为gRPC.Server。用NuGet安装“Grpc.AspNetCore”。新建一个Protos文件夹来存放.proto文件,新建一个名为Message.proto的文件来定义通信过程中需要的message。
1 syntax = "proto3"; //给编译器指明语法为proto3
2
3 //员工
4 message Employee{
5 int32 Id = 1; //Id
6 string Name = 2; //姓名
7 int32 EmployeeNo = 3; //工号
8 Gender Gender = 4; //性别
9 Date BirthDay = 5; //生日
10 string Department = 6; //部门
11 bool IsValid = 7; //有效性
12 bytes Photo = 8; //照片
13 }
14 //性别(枚举)
15 enum Gender{
16 NOT_SPESIFICED = 0;
17 FEMALE = 1;
18 MALE = 2;
19 }
20 //日期
21 message Date{
22 int32 Year = 1;
23 int32 MOnth= 2;
24 int32 Day = 3;
25 }
26
27 //Service 用的参数:
28 //根据Id查询员工信息
29 message GetEmployeeByIdRequest{
30 int32 Id = 1;
31 }
32 //上传的员工信息请求
33 message EmployeeRequest{
34 Employee Employee = 1;
35 }
36 //返回员工信息
37 message EmployeeResponse{
38 Employee Employee = 1;
39 }
40 //根据条件查询员工
41 message GetEmployeeCollectionRequest{
42 string SearchTerm = 1;
43 bool IsValid = 2;
44 }
45 //返回员工信息集合
46 message GetEmployeeCollectionReponse{
47 Employee Employee = 1;
48 }
49 //上传员工照片
50 message AddPhotoRequest{
51 bytes Photo = 1;
52 }
53 //上传员工照片响应
54 message AddPhotoReponse{
55 bool IsOK = 1;
56 }
message.proto