常见的请求过程是:客户端发起请求 > 服务器处理请求 > 服务器返回请求,客户端只发送一次数据,服务器也只返回一次数据。而双向流可以在一次请求中让客户端发送多次数据,服务端返回多次数据。
一、添加双向流
1. 修改greet.proto
替换为以下内容:
syntax = "proto3";
option csharp_namespace = "GrpcDemo.Service";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
rpc Bothway(stream ToMessage) returns(stream BackMessage);
}
// 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 ToMessage{
string message = 1;
}
message BackMessage{
string message = 1;
}
添加了一个Bothway方法,该方法参数为ToMessage,返回BackMessage。使用 stream
修饰,声明为流式传输。
2. 实现Bothway
修改 GrpcDemo.Service > GreeterService.cs,实现Bothway方法:
/// <summary>
/// 双向流
/// </summary>
/// <param name="requestStream">请求流</param>
/// <param name="responseStream">响应流</param>
/// <param name="context">上下文</param>
/// <returns></returns>
public override async Task Bothway(IAsyncStreamReader<ToMessage> requestStream, IServerStreamWriter<BackMessage> responseStream, ServerCallContext context)
{
while (!context.CancellationToken.IsCancellationRequested && await requestStream.MoveNext())
{
string msg = requestStream.Current.Message;
Console.WriteLine($"加载模块:{msg}");
await Task.Delay(500);
if (!context.CancellationToken.IsCancellationRequested)
{
await responseStream.WriteAsync(new BackMessage()
{
Message = $"{msg}模块加载完成"
});
}
}
}
3. 编写客户端
修改 GrpcDemo.Client > Program.cs
private const string ServiceAddress = "https://localhost:5001";
static void Main(string[] args)
{
var task = Bothway();
task.Wait();
Console.ReadKey();
}
//发送模块的个数
const int count = 10;
public async static Task Bothway()
{
//创建Grpc通道
var channel = GrpcChannel.ForAddress(ServiceAddress);
//创建Greeter客户端
var client = new Greeter.GreeterClient(channel);
//创建双向流对象
var bothway = client.Bothway();
//CancellationTokenSource 管理是否关闭流
//CancellationTokenSource.CancelAfter() 规定时间关闭流
//CancellationTokenSource.Cancel() 立即关闭流
var cts = new CancellationTokenSource();
//响应事件
var backTask = Task.Run(async () =>
{
int current = 0;
try
{
//从响应流获取数据(cts.Token: 是否关闭流)
while (await bothway.ResponseStream.MoveNext(cts.Token))
{
current++;
var back = bothway.ResponseStream.Current;
Console.WriteLine($"{back.Message},加载进度{((double)current / count) * 100}%");
if (current >= 5)
{
//关闭流
cts.Cancel();
}
}
}
//关闭流异常捕获
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
{
Console.WriteLine("Stream cancelled.");
}
});
for (int i = 0; i < count; i++)
{
//请求流写入数据
await bothway.RequestStream.WriteAsync(new ToMessage()
{
Message = i.ToString()
});
}
//等待发送完成
await bothway.RequestStream.CompleteAsync();
Console.WriteLine("加载模块发送完毕");
Console.WriteLine("等待加载...");
//等待响应完成
await backTask;
Console.WriteLine("模块已全部加载完毕");
}
注意:以上代码如果出现缺少引用找不到类的问题,请重新生成解决方案或项目。
二、运行并查看结果
输出:
可以看到一个双向流的过程了,加载模块5完成后关闭流,输出了Stream cancelled