gRPC使用

gRPC

gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架。 gRPC使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建。它使用HTTP/2作为通信协议,使用 Protocol Buffers 作为序列化协议。

gRPC 的优势

gRPC 使用 HTTP/2 作为传输协议。 虽然与 HTTP 1.1 也能兼容,但 HTTP/2 具有许多高级功能:

  • 用于数据传输的二进制组帧协议 - 与 HTTP 1.1 不同,HTTP 1.1 是基于文本的。
  • 对通过同一连接发送多个并行请求的多路复用支持 - HTTP 1.1 将处理限制为一次处理一个请求/响应消息。
  • 双向全双工通信,用于同时发送客户端请求和服务器响应。
  • 内置流式处理,支持对大型数据集进行异步流式处理的请求和响应。
  • 减少网络使用率的标头压缩。

gRPC 是轻量型且高性能的。 其处理速度可以比 JSON 序列化快 8 倍,消息小 60% 到 80%。 在WCF中,gRPC 的性能超过经过高度优化的 NetTCP 绑定的速度和效率。 与偏向于 Microsoft 堆栈的 NetTCP 不同,gRPC 是跨平台的。

四种通信模式及其使用场景

服务类型 特点
简单RPC 简单rpc调用,传入一个请求对象,返回一个返回对象
服务端流式RPC 传入一个请求对象,服务端可以返回多个结果对象
客户端流式RPC 客户端传入多个请求对象,服务端返回一个结果对象
双向流式RPC 结合客户端流式RPC和服务端流式RPC,可以传入多个请求对象,返回多个结果对象

Unary PRC

 rpc UnaryCall (ExampleRequest) returns (ExampleResponse);

客户端发起一次请求,服务端响应一个数据,即标准RPC通信。
这种模式,一个每一次都是发起一个独立的tcp连接,走一次三次握手和四次挥手!

Server streaming RPC

rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);

服务端流 RPC 下,客户端发出一个请求,但不会立即得到一个响应,而是在服务端与客户端之间建立一个单向的流,服务端可以随时向流中写入多个响应消息,最后主动关闭流,而客户端需要监听这个流,不断获取响应直到流关闭

应用场景:股票大盘走势

Client streaming RPC

rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);

应用场景:物联网终端向服务器报送数据。

Bi-directional streaming RPC

rpc GetOrderNO(stream  OrderRequest) returns ( stream OrderReply);

应用场景:聊天应用

gRPC 示例

新建gRPC 项目,GrpcDemo.Service

创建完成后,看下项目中的代码

  1. 配置文件appsettings.json多了Kestrel启用 HTTP/2 的配置,因为 gRPC 是基于HTTP/2来通信的
  1. csproj中增加了包含Protobuf

后面如果需要新增proto,需要手动添加,这边一次性改成通配符

<Protobuf Include="Protos\*.proto" GrpcServices="Server" />
  1. Program中将gRPC服务添加到了终结点路由中
app.MapGrpcService<GreeterService>();
  1. 查看PB协议文件greet.proto
  1. 服务类GreeterService,服务类集成的Greeter.GreeterBase


创建gRPC客户端

添加包Google.Protobuf、Grpc.Net.Client、Grpc.Tools

项目文件中添加GrpcDemo.Service的proto文件

<Protobuf Include="..\GrpcDemo.Service\Protos\*.proto" GrpcServices="Client" 
Link="Protos\%(RecursiveDir)%(Filename)%(Extension)" />

Program中调用服务

using Grpc.Core;
using Grpc.Net.Client;
using GrpcDemo.Service;

//创建grpc通道
var channel = GrpcChannel.ForAddress("http://localhost:5274");

//创建Greeter服务客户端
var greetClient = new Greeter.GreeterClient(channel);
HelloReply reply = greetClient.SayHello(new HelloRequest()
{
    Name = "Curry"
});
Console.WriteLine(reply.Message);
Console.ReadLine();

运行程序,测试

Client发起了HTTP/2 POST请求,服务端返回了Hello Curry。

新增Proto文件

新增PB协议文件


由于我们已经处理过项目文件,此处不需要过多处理,直接修改文件

syntax = "proto3";
option csharp_namespace = "GrpcDemo.Service";

//标识proto文件的命名空间
package order;

//定义服务
service Order {
  //发送
  rpc GetOrderInfo (OrderReq) returns (OrderResp);
}

message OrderReq{
    string OrderNo=1;
}

message OrderResp{
    string Result=1;
}

生成解决方案后,新增Service

using Grpc.Core;
using GrpcDemo.Service;

namespace GrpcDemo.Service.Services
{
    public class OrderService : Order.OrderBase
    {
        private readonly ILogger<OrderService> _logger;
        public OrderService(ILogger<OrderService> logger)
        {
            _logger = logger;
        }

        public override Task<OrderResp> GetOrderInfo(OrderReq request, ServerCallContext context)
        {
            Console.WriteLine($"接收到参数了:{request.OrderNo}");
            return Task.FromResult(new OrderResp
            {
                Result = "获取到的传入参数是: " + request.OrderNo
            });
        }
    }
}

program中将服务增加到路由

app.MapGrpcService<OrderService>();

客户端调用测试

调用完成,测试正常

服务流、客户流和双向流的使用

//定义服务
service Greeter {
  //一元
  rpc UnaryCall (ExampleRequest) returns (ExampleResponse);
  //服务流
  rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);
  //客户流
  rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);
  //双向流
  rpc GetOrderNO(stream  ExampleRequest) returns ( stream ExampleResponse);
}

message ExampleRequest {
    int32 pageIndex = 1;
    int32 pageSize = 2;
}

message ExampleResponse{
    string result=1;
}

具体方法实现

//服务流
public override async Task StreamingFromServer(ExampleRequest request, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    int i = 0;
    while (!context.CancellationToken.IsCancellationRequested)
    {
        i++;
        await responseStream.WriteAsync(new ExampleResponse()
        {
            Result = "现在的数值是:" + i
        });
        await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
    }
}

//客户流
public override async Task<ExampleResponse> StreamingFromClient(IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)
{
    int result = 0;
    while (await requestStream.MoveNext())
    {
        result += requestStream.Current.PageSize * requestStream.Current.PageIndex;
    }
    return await Task.FromResult(new ExampleResponse() { Result = result.ToString() });
}

//双向流
public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    while (!context.CancellationToken.IsCancellationRequested && await requestStream.MoveNext())
    {
        int index = requestStream.Current.PageIndex;
        int size = requestStream.Current.PageSize;
        Console.WriteLine($"传入参数,PageIndex:{index},PageSize:{size}");
        if (!context.CancellationToken.IsCancellationRequested)
        {
            await responseStream.WriteAsync(new ExampleResponse()
            {
                Result = $"index*size={index * size}"
            });
        }
    }
}

客户端调用

//服务流
async static Task StreamingFromServerTest()
{
    //创建gRPC通道
    var channel = GrpcChannel.ForAddress(ServiceAddress);
    //创建Greeter服务客户端
    var greetClient = new Greeter.GreeterClient(channel);

    var result = greetClient.StreamingFromServer(new ExampleRequest()
    {
        PageIndex = 1,
        PageSize = 10
    });
    var cts = new CancellationTokenSource();

    var iter = result.ResponseStream; // 拿到响应流
    int num = 0;
    try
    {
        while (await iter.MoveNext(cts.Token)) // 迭代
        {
            num++;
            Console.WriteLine(iter.Current.Result);  // 将数据写入到文件流中
            if (num >= 5)
            {
                cts.Cancel();//调用5次后自动取消
            }
        }
    }
    catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
    {
        Console.WriteLine("Stream cancelled.");
    }

    //输出返回结果
    Console.WriteLine("OK");
}

//客户流
async static Task StreamingFromClientTest()
{
    //创建gRPC通道
    var channel = GrpcChannel.ForAddress(ServiceAddress);
    //创建Greeter客户端
    var client = new Greeter.GreeterClient(channel);
    //创建客户流对象
    var clientStream = client.StreamingFromClient();
    for (var i = 1; i < 10; i++)
    {
        await clientStream.RequestStream.WriteAsync(new ExampleRequest() { PageIndex = i, PageSize = 10 });
    }
    await clientStream.RequestStream.CompleteAsync();
    var resut = await clientStream;
    Console.WriteLine($"客户端流请求结果{resut.Result}");
}

//双向流
async static Task StreamingBothWaysTest()
{
    //创建gRPC通道
    var channel = GrpcChannel.ForAddress(ServiceAddress);
    //创建Greeter客户端
    var client = new Greeter.GreeterClient(channel);
    //创建双向流对象
    var GetOrderNO = client.StreamingBothWays();
    //CancellationTokenSource 管理是否关闭流
    //CancellationTokenSource.CancelAfter() 规定时间关闭流
    //CancellationTokenSource.Cancel() 立即关闭流
    var cts = new CancellationTokenSource();
    //响应事件
    var backTask = Task.Run(async () =>
    {
        int current = 0;
        try
        {
            //从响应流获取数据(cts.Token: 是否关闭流)
            while (await GetOrderNO.ResponseStream.MoveNext(cts.Token))
            {
                current++;
                var back = GetOrderNO.ResponseStream.Current;
                Console.WriteLine($"{back.Result},加载进度{((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 GetOrderNO.RequestStream.WriteAsync(new ExampleRequest()
        {
            PageIndex = i,
            PageSize = 10
        });
    }

    //等待发送完成
    await GetOrderNO.RequestStream.CompleteAsync();

    Console.WriteLine("等待加载...");

    //等待响应完成
    await backTask;

    Console.WriteLine("已全部加载完毕");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • grpc 整理(nodejs) gRPC 是什么? 在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台...
    秋枫残红阅读 1,722评论 0 2
  • 一、grpc的golang环境配置protoc的安装比较简单,golang使用protoc-gen-go进行pro...
    罗文才阅读 2,342评论 0 0
  • RPC 远程过程调用 可以区别于IPC A想要调用B服务器上的提供的函数/方法 单一 RPC 无法实现 push,...
    转身是天涯阅读 5,788评论 3 5
  • 核心工作 在一个 .proto 文件内定义服务. 用 protocol buffer 编译器生成服务器和客户端代码...
    迷糊银儿阅读 7,378评论 0 1
  • 一、gRPC简介 gRPC(Remote Produce Call)是google开发的一个高性能、开源的通...
    Software泥瓦匠阅读 693评论 0 0