GRPC技术介绍和技术实现
1.前言
gRPC,其实就是RPC框架的一种。RPC框架是什么,RPC框架其实就是一种能够让开发像调用本地方法一样去调用远程方法,和webservice的调用方法非常类似。g也有global的意思,意思是全球化,是一个高性能、开源和支持多语言开发的 RPC 框架。
2.GRPC简介
2.1RPC是什么
在说GRPC之前,我们先说REST通信。当前的软件行业,在设计软件产品的时候,开发人员可能考虑的更多的是基于REST的通信。REST通信是非常灵活的一种方式,将资源发布在网络,由客户端通过http或者http2协议与资源进行交互,现在的微服务基本上都是运用这种技术框架。
打个比方,REST通讯就像在大街开个店铺,大家想从这里获取什么,只要访问就行,知道地址就可以访问,然后买买买。这种方式很灵活,服务器就相当于店铺,webservice就是这总类型
而RPC是和REST不同的一种通信架构。
RPC 最终的效果是让开发人员的可以像调用本地方法一样调用远程服务提供的方法。Grpc是RPC的一种,目前许多公司按照自己的需求和需要设计了自己的RPC通信架构,GRPC就是由谷歌研发的一个RPC通信框架。g代表global全球化的意思。它是一种高性能、支持多种开发语言的通信架构。
如果REST通讯就像在大街开个店铺,那么GRPC通讯就像打电话,像对讲机,像早期谍战剧的发报机。REST通讯基本上对客户没什么要求,我外面开个店铺,你要来买东西的话,人来就行。但是GRPC不行,对两个人通讯有要求,两人手上都要有电话,都要有发报机。
2.2GRPC技术实现过程
上面说过GRPC像发报机,那么实现发报需要什么呢?
1.密码本,用于翻译;
2.两个发报机,用于收发;
3.翻译的方法,单纯的密码本还需要翻译方法配合;
- 使用gprc一定会创建.protobuf文件,这个就是密码本,也是整个GRPC的基础:
- 定义标准的proto文件后,用这个文件来生成标准代码。生成的方法一共两种,cmd和vs编译,下文的实操例子会给出方法。生成的标准代码就是发报机,分别将代码放在客户端和服务端。两个发报机就OK了
- 其实GRPC的信息翻译,就是protobuf的序列化和反序列化,引用的库包已经将这个过程包含了;
2.3Grpc大致请求流程:
1、客户端(gRPC client)调用方法,发起RPC调用。
2、对请求信息使用 Protobuf 进行对象序列化,信息是二进制的。
3、服务端(gRPC Server)接收到请求后,反序列化请求信息,进行业务逻辑处理并返回。
4、对响应结果使用 Protobuf 进行对象序列化。
5、客户端接受到服务端响应,反序列化返回的信息。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。
2.4gRPC的特性
grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言
基于 IDL ( 接口定义语言(Interface Define Language))文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端Stub;
通信协议基于标准的 HTTP/2 设计,支持·双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
安装简单,扩展方便(用该框架每秒可达到百万个RPC)
3.gRPC优势和劣势
3.1优势
性能
Protobuf序列化和反序列化的速度,JSON的5倍。
gRPC是为HTTP/2而设计的,它是HTTP的一个主要版本,与HTTP 1.x相比具有显著的性能优势:二进制框架和压缩。HTTP/2协议在发送和接收方面都很紧凑和高效。通过单个TCP连接复用多个HTTP/2调用。多路复用消除了线头阻塞。
3.2劣势
浏览器支持有限
当下,不可能直接从浏览器调用gRPC服务。gRPC大量使用HTTP/2功能,没有浏览器提供支持gRPC客户机的Web请求所需的控制级别。gRPC Web并非支持所有gRPC功能。不支持客户端和双向流,并且对服务器流的支持有限。
不是人类可读的
HTTP API请求以文本形式发送,可以由人读取和创建。
但它的二进制格式是不可读的。
4.GRPC实例
4.1创建protobuf文件
我们新建一个myGrpc.proto文件,文本如下图所示
代码如下:
syntax = “proto3”;
option csharp_namespace = “GrpcService1.Services”;
package MyGrpc;
service MyGrpcService {
rpc SendMsg(GrpcData) returns (GrpcResult);
rpc ReceiveDataFromServer(GrpcData) returns (stream GrpcData);
}
message GrpcData {
string Ip = 1;
int32 Port = 2;
string DataId = 3;
int32 DataPriority = 4;
enum DataType {
Info = 0;
Error = 1;
}
DataType Dtype = 5;
repeated string Conntent = 6;
string NodeName = 7;
}message GrpcResult{
bool result = 1;
string tip=2;
}
针对这个协议文档,这里简单解释一下
protobuf有2个版本,默认版本是 proto2,如果需要 proto3,则需要在非空非注释第一行使用 syntax = “proto3” 标明版本。
Proto2和proto3的区别很多,除了各种自身优化之外,最重要的是增加了Go,Ruby,JavaNano。
这里的message是关键字,相当于我们的class,用于定于类。GrpcData是类型名,里面的是它的属性。
每个字段的修饰符默认是 singular,一般省略不写,repeated 表示字段可重复,即用来表示数组类型。
每个字符=后面的数字称为标识符,注意,这里不是字段值。每个字段都需要提供一个唯一的标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不能够再改变,标识符的取值范围为 [1, 2^29 - 1] 。
.proto 文件可以写注释,单行注释 //,多行注释 /* … */
一个 .proto 文件中可以写多个消息类型,即对应多个结构体(struct)。
枚举,枚举的第一个值必须为0;这里普及一下枚举的别名别名(Alias),允许为不同的枚举值赋予相同的标识符,称之为别名,需要在枚举的第一个值之前打开allow_alias选项,代码:“option allow_alias = true;”。
4.2编译proto文件
首先,创建一个类库项目。目的是为了编译proto文件。
同时创建两个控制台项目——client和server,用于等下测试grpc
然后添加这四个类库。全部是用于GRPC项目的,第三个包主要用于proto文件编译,有兴趣的人可以去尝试一下通过GRPC.tool用cmd去编译proto文件,这边不推荐这种方式。
将之前的protobuf文件复制进来
并编辑项目文件
只有改成Protobuf,在编译项目的时候才会自动编译协议文件。如图所示。
到这里我们第一步就完成了,接下来就要测试grpc的通信
4.3服务端实现
首先,将两个文件MyGrpc.cs和MyGrpcGrpc.cs复制到grpcclient和grpcserver两个项目下
我们先处理服务端
创建MsgService继承MyGrpcServiceBase
为什么这里要继承,两个点:1.我们定的协议文件根本没有详细的方法实现,大家回顾一下我们的协议文件就知道,里面只是声明方法,并没有实现;所以这个方法要我们去实现;
打开编译成的MyGrpcGrpc.cs文件,可以看到sendmessage是一个抽象类,并且方法都是直接抛出异常,所以这个不能直接使用。我们再编译完protobuf之后,其实形成的不是一个可用的方法,而是一个抽象,一个接口。
重写MyGrpcServiceBase的方法
方法重写完之后,就完了吗,还没有。我们只是将接口方法实现了,我们还没部署服务。打开Program.cs,部署服务的代码如下
const int Port = 9007;
static void Main(string[] args)
{
Server server = new Server
{
Services = { MyGrpcService.BindService(new MsgService()) },
Ports = { new ServerPort(“localhost”, Port, ServerCredentials.Insecure) }
};
server.Start();
Console.WriteLine("gRPC server listening on port " + Port);
Console.WriteLine(“任意键退出…”);
Console.ReadKey();
server.ShutdownAsync().Wait();
}
接下来处理客户端的方法
客户端的代码比较简单,打开program.cs
Channel channel = new Channel(“127.0.0.1:9007”, ChannelCredentials.Insecure);
var client = new MyGrpcServiceClient(channel);
GrpcData grpcData = new GrpcData();
for(int i=0;i<30;i++)
{
grpcData.NodeName = “信息发送”+i.ToString();
var reply = client.SendMsg(grpcData);
Console.WriteLine(reply.Tip);
}
channel.ShutdownAsync().Wait();
Console.WriteLine(“任意键退出…”);
Console.ReadKey();
分别运行两个项目,先打开grpcserver,再打开grpcclient,通讯效果如下
这样子我们的实例就完成了。