系列章节
概述
GRPC的数据交互模式有:
1.单项RPC,最简单的数据交换方式,客户端发出单个请求,收到单个响应
2.服务端流式RPC,是在服务端收到客户端的请求之后,返回一个应答流,客户端收到流之后处理。
3.客户端流式RPC,与单项类似,但客户端发送的是流式RPC
4.双向流式RPC,调用由客户端调用方法来初始化,而服务端则接收到客户端的元数据,方法名和截止时间。服务端可以选择发送回它的初始元数据或等待客户端发送请求。下一步怎样发展取决于应用,因为客户端和服务端能在任意顺序上读写 - 这些流的操作是完全独立的。例如服务端可以一直等直到它接收到所有客户端的消息才写应答,或者服务端和客户端可以像"乒乓球"一样:服务端后得到一个请求就回送一个应答,接着客户端根据应答来发送另一个请求,以此类推。
单项RPC较简单不做示例了。
首先在vs2019中net core3.0中新建GRPC项目。然后定义响应的proto文件,根据proto文件生成响应的服务端与客户端代码。
1.服务端流式RPC
1.定义 protofile
syntax = "proto3"; option csharp_namespace = "GrpcGreeter"; package Greet; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} rpc GetStreamContent (StreamRequest) returns (stream StreamContent) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings. message HelloReply { string message = 1; } message StreamRequest { string fileName = 1; } message StreamContent { bytes content = 1; }
2.实现服务端Service
重新生成项目,然后实现GetStreamContent,简单的读取文件内容,并将内容返回给Client
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Google.Protobuf; using Grpc.Core; namespace GrpcGreeter { public class GreeterService : Greeter.GreeterBase { public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } public override Task GetStreamContent(StreamRequest request, IServerStreamWriter<StreamContent> responseStream, ServerCallContext context) { return Task.Run(async () => { using (var fs = File.Open(request.FileName, FileMode.Open)) // 从 request 中读取文件名并打开文件流 { var remainingLength = fs.Length; // 剩余长度 var buff = new byte[1048576]; // 缓冲区,这里我们设置为 1 Mb while (remainingLength > 0) // 若未读完则继续读取 { var len = await fs.ReadAsync(buff); // 异步从文件中读取数据到缓冲区中 remainingLength -= len; // 剩余长度减去刚才实际读取的长度 // 向流中写入我们刚刚读取的数据 await responseStream.WriteAsync(new StreamContent { Content = ByteString.CopyFrom(buff, 0, len) }); } } }); } } }
3.实现Client
新建一个netcore 3.0的Console项目,并引入Nuget包
Install-Package Grpc.Net.Client -Version 0.1.22-pre1 Install-Package Google.Protobuf -Version 3.8.0 Install-Package Grpc.Tools -Version 1.22.0
编辑项目文件,修改如下节点
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
重新生成项目,Client端主要实现发送请求,请求是一个服务器端的文件路径。然后实现接收服务端的流,并保存到Client本地。
using Grpc.Net.Client; using GrpcGreeter; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; namespace GrpcGreeterClient { class Program { static async System.Threading.Tasks.Task Main(string[] args) { AppContext.SetSwitch( "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var httpClient = new HttpClient(); // The port number(50051) must match the port of the gRPC server. httpClient.BaseAddress = new Uri("http://localhost:50051"); var client = GrpcClient.Create<Greeter.GreeterClient>(httpClient); // var reply = await client.SayHelloAsync( new HelloRequest { Name = "GreeterClient" }); Console.WriteLine("Greeting: " + reply.Message); Console.ReadKey(); // var result = client.GetStreamContent(new StreamRequest { FileName = @"D:\Docs.zip" }); //发送请求 var iter = result.ResponseStream; using (var fs = new FileStream(@"D:\Docs2.zip", FileMode.Create)) // 新建一个文件流用于存放我们获取到数据 { while (await iter.MoveNext()) // 迭代 { iter.Current.Content.WriteTo(fs); // 将数据写入到文件流中 } } Console.ReadKey(); } } }
文件生成成功
2.客户端流式RPC
1.定义 protofile
syntax = "proto3"; option csharp_namespace = "GRPC.TEST"; package Greet; // The greeting service definition. service Greeter { rpc getResult (stream Value) returns (Result) {} } //定义Value消息类型,用于客户端消息 message Value { int32 value = 1; } //定义Result消息类型,包含总和,数字数量和平均值,用于服务端消息返回 message Result { int32 sum = 1; int32 cnt = 2; double avg = 3; }
2.实现服务端Service
重新生成项目,并实现如下
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Google.Protobuf; using Grpc.Core; namespace GRPC.TEST { public class GreeterService : Greeter.GreeterBase { public override async Task<Result> getResult(IAsyncStreamReader<Value> requestStream, ServerCallContext context) { while (await requestStream.MoveNext()) { var point = requestStream.Current; } return new Result { Sum = 1 }; } } }
3.实现Client
新建一个netcore 3.0的Console项目,并引入Nuget包,安装nuget包与其他操作同上一个例子,实现代码如下
using Grpc.Net.Client; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; namespace GRPC.TEST.CLIENT { class Program { static async System.Threading.Tasks.Task Main(string[] args) { AppContext.SetSwitch( "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var httpClient = new HttpClient(); // The port number(50051) must match the port of the gRPC server. httpClient.BaseAddress = new Uri("http://localhost:50051"); var client = GrpcClient.Create<Greeter.GreeterClient>(httpClient); using (var call = client.getResult()) { await call.RequestStream.WriteAsync(new Value { Value_ = 1 }); await call.RequestStream.CompleteAsync(); var response = await call.ResponseAsync; } Console.ReadKey(); } } }
3.双向流式RPC
1.定义proto
syntax = "proto3"; option csharp_namespace = "GRPC.TEST"; package Greet; // The greeting service definition. service Greeter { rpc getResult (stream Value) returns (stream Result) {} } //定义Value消息类型,用于客户端消息 message Value { int32 value = 1; } //定义Result消息类型,包含总和,数字数量和平均值,用于服务端消息返回 message Result { int32 sum = 1; int32 cnt = 2; double avg = 3; }
2.服务端实现
重新生成项目,并实现如下
public override async Task getResult(IAsyncStreamReader<Value> requestStream, IServerStreamWriter<Result> responseStream, ServerCallContext context) { while (await requestStream.MoveNext()) { var note = requestStream.Current; await responseStream.WriteAsync(new Result { Sum = 100 }); } }
3.客户端代码
新建一个netcore 3.0的Console项目,并引入Nuget包,安装nuget包与其他操作同上一个例子,实现代码如下
using Grpc.Net.Client; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace GRPC.TEST.CLIENT { class Program { static async System.Threading.Tasks.Task Main(string[] args) { AppContext.SetSwitch( "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var httpClient = new HttpClient(); // The port number(50051) must match the port of the gRPC server. httpClient.BaseAddress = new Uri("http://localhost:50051"); var client = GrpcClient.Create<Greeter.GreeterClient>(httpClient); using (var call = client.getResult()) { var responseReaderTask = Task.Run(async () => { while (await call.ResponseStream.MoveNext()) { var note = call.ResponseStream.Current; Console.WriteLine("Received " + note); } }); await call.RequestStream.WriteAsync(new Value { Value_ = 12 }); await call.RequestStream.CompleteAsync(); await responseReaderTask; } Console.ReadKey(); } } }
至此,GRPC的几种数据交互分享完毕