zoukankan      html  css  js  c++  java
  • ASP.Net Core 3.1 使用gRPC入门指南

    主要参考文章微软官方文档: https://docs.microsoft.com/zh-cn/aspnet/core/grpc/client?view=aspnetcore-3.1

    此外还参考了文章 https://www.cnblogs.com/stulzq/p/11581967.html并写了一个demo: https://files.cnblogs.com/files/hudean/GrpcDemo.zip 

    damo github地址: https://github.com/hudean/GrpcDemo

    一、简介

    gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。

    gRPC 的主要优点是:

    • 现代高性能轻量级 RPC 框架。
    • 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。
    • 可用于多种语言的工具,以生成强类型服务器和客户端。
    • 支持客户端、服务器和双向流式处理调用。
    • 使用 Protobuf 二进制序列化减少对网络的使用。

    这些优点使 gRPC 适用于:

    • 效率至关重要的轻量级微服务。
    • 需要多种语言用于开发的 Polyglot 系统。
    • 需要处理流式处理请求或响应的点对点实时服务。

    二、创建 gRPC 服务

    • 启动 Visual Studio 并选择“创建新项目”。 或者,从 Visual Studio“文件”菜单中选择“新建” > “项目” 。

    • 在“创建新项目”对话框中,选择“gRPC 服务”,然后选择“下一步” :

      Visual Studio 中的“创建新项目”对话框

    • 将项目命名为 GrpcGreeter。 将项目命名为“GrpcGreeter”非常重要,这样在复制和粘贴代码时命名空间就会匹配。

    • 选择“创建”。

    • 在“创建新 gRPC 服务”对话框中:

      • 选择“gRPC 服务”模板。
      • 选择“创建”。

    运行服务

    • 按 Ctrl+F5 以在不使用调试程序的情况下运行。

      Visual Studio 会显示以下对话框:

      此项目配置为使用 SSL。

      如果信任 IIS Express SSL 证书,请选择“是” 。

      将显示以下对话框:

      安全警告对话

      如果你同意信任开发证书,请选择“是”。

    日志显示该服务正在侦听 https://localhost:5001

    控制台显示如下:

    info: Microsoft.Hosting.Lifetime[0]
          Now listening on: https://localhost:5001
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Development
    

     备注

    gRPC 模板配置为使用传输层安全性 (TLS)。 gRPC 客户端需要使用 HTTPS 调用服务器。

    macOS 不支持 ASP.NET Core gRPC 及 TLS。 在 macOS 上成功运行 gRPC 服务需要其他配置。 

    检查项目文件

    GrpcGreeter 项目文件:

    • greet.proto : Protos/greet.proto 文件定义 Greeter gRPC,且用于生成 gRPC 服务器资产。 
    • Services 文件夹:包含 Greeter 服务的实现。
    • appSettings.json :包含配置数据,例如 Kestrel 使用的协议。 
    • Program.cs:包含 gRPC 服务的入口点。 

    Startup.cs :包含配置应用行为的代码。

    上述准备工作完成,开始写gRPC服务端代码!



    example.proto文件内容如下

    syntax = "proto3";
    
    option csharp_namespace = "GrpcGreeter";
    
    package example;
    
    service exampler {
      // Unarys
      rpc UnaryCall (ExampleRequest) returns (ExampleResponse);
    
      // Server streaming
      rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);
    
      // Client streaming
      rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);
    
      // Bi-directional streaming
      rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse);
    }
    message ExampleRequest {
        int32 id = 1;
        string name = 2;
    }
    
    message ExampleResponse {
        string msg = 1;
    }
    example.proto

    其中:

    syntax = "proto3";是使用 proto3 语法,protocol buffer 编译器默认使用的是 proto2 。 这必须是文件的非空、非注释的第一行。

    对于 C#语言,编译器会为每一个.proto 文件创建一个.cs 文件,为每一个消息类型都创建一个类来操作。

    option csharp_namespace = "GrpcGreeter";是c#代码的命名空间

    package example;包的命名空间

    service exampler 是服务的名字

    rpc UnaryCall (ExampleRequest) returns (ExampleResponse); 意思是rpc调用方法 UnaryCall 方法参数是ExampleRequest类型 返回值是ExampleResponse 类型

    message ExampleRequest {
        int32 id = 1;
        string name = 2;
    }
    指定字段类型 在上面的例子中,所有字段都是标量类型:一个整型(id),一个string类型(name)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。 分配标识号 我们可以看到在上面定义的消息中,给每个字段都定义了唯一的数字值。这些数字是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留
    [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。 最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。类似地,你不能使用之前保留的任何标识符。 指定字段规则 消息的字段可以是一下情况之一: singular(默认):一个格式良好的消息可以包含该段可以出现 0 或 1 次(不能大于 1 次)。 repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。 默认情况下,标量数值类型的repeated字段使用packed的编码方式。

      

    在GrpcGreeter.csproj文件添加:

    <ItemGroup>
    <Protobuf Include="Protosexample.proto" GrpcServices="Server" />
    </ItemGroup>

    点击保存 

    在Services文件夹下添加ExampleService类,代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Grpc.Core;
    
    namespace GrpcGreeter
    {
        public class ExampleService :exampler.examplerBase
        {
            /// <summary>
            /// 一元方法以参数的形式获取请求消息,并返回响应。 返回响应时,一元调用完成。
            /// </summary>
            /// <param name="request"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override Task<ExampleResponse> UnaryCall(ExampleRequest request, ServerCallContext context)
            {
                // return base.UnaryCall(request, context);
                return Task.FromResult(new ExampleResponse
                {
                    Msg = "id :" + request.Id + "name : " + request.Name + " hello"
    
                }) ;
            }
    
            /// <summary>
            /// 服务器流式处理方法
            /// 服务器流式处理方法以参数的形式获取请求消息。 由于可以将多个消息流式传输回调用方,因此可使用 responseStream.WriteAsync 发送响应
            /// 消息。 当方法返回时,服务器流式处理调用完成。
            /// </summary>
            /// <param name="request"></param>
            /// <param name="responseStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task StreamingFromServer(ExampleRequest request, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
            {
    
                /**
                 * 服务器流式处理方法启动后,客户端无法发送其他消息或数据。 某些流式处理方法设计为永久运行。 对于连续流式处理方法,客户端可以在*再需要调用时将其取消。 当发生取消时,客户端会将信号发送到服务器,并引发 ServerCallContext.CancellationToken。 应在服务器上通过异步方法使用 CancellationToken 标记,以实现以下目的:
                   所有异步工作都与流式处理调用一起取消。
                   该方法快速退出。
                 **/
                //return base.StreamingFromServer(request, responseStream, context);
    
                //for (int i = 0; i < 5; i++)
                //{
                //    await responseStream.WriteAsync(new ExampleResponse { Msg = "我是服务端流for:" + i });
                //    await Task.Delay(TimeSpan.FromSeconds(1));
                //}
                int index = 0;
                while (!context.CancellationToken.IsCancellationRequested)
                {
                    index++;
                    await responseStream.WriteAsync(new ExampleResponse { Msg = "我是服务端流while" + index+" "+request.Id+" "+request.Name });
                    await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
    
                }
    
            }
    
    
            /// <summary>
            /// 客户端流式处理方法
            /// 客户端流式处理方法在该方法没有接收消息的情况下启动。 requestStream 参数用于从客户端读取消息。 返回响应消息时,客户端流式处理调用
            /// 完成:
            /// </summary>
            /// <param name="requestStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task<ExampleResponse> StreamingFromClient(IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)
            {
                // return base.StreamingFromClient(requestStream, context);
                List<string> list = new List<string>();
                while (await requestStream.MoveNext())
                {
                    //var message = requestStream.Current;
                    var id = requestStream.Current.Id;
                    var name = requestStream.Current.Name;
    
                    list.Add($"{id}-{name}");
                    // ...
                }
                return new ExampleResponse() { Msg = "我是客户端流while"+string.Join(',',list) };
    
    
                //await foreach (var message in requestStream.ReadAllAsync())
                //{
                //    // ...
                //}
                // return new ExampleResponse() { Msg= "我是客户端流foreach" };
            }
    
            /// <summary>
            /// 双向流式处理方法
            /// 双向流式处理方法在该方法没有接收到消息的情况下启动。 requestStream 参数用于从客户端读取消息。 
            /// 该方法可选择使用 responseStream.WriteAsync 发送消息。 当方法返回时,双向流式处理调用完成:
            /// </summary>
            /// <param name="requestStream"></param>
            /// <param name="responseStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
            {
                //return base.StreamingBothWays(requestStream, responseStream, context);
                await foreach (var message in requestStream.ReadAllAsync())
                {
                   string str= message.Id + " " + message.Name;
                    await responseStream.WriteAsync(new ExampleResponse() { Msg="我是双向流:"+ str });
                    
                }
    
    
                //// Read requests in a background task.
                //var readTask = Task.Run(async () =>
                //{
                //    await foreach (var message in requestStream.ReadAllAsync())
                //    {
                //        // Process request.
                //        string str = message.Id + " " + message.Name;
                //    }
                //});
    
                //// Send responses until the client signals that it is complete.
                //while (!readTask.IsCompleted)
                //{
                //    await responseStream.WriteAsync(new ExampleResponse());
                //    await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
                //}
            }
    
        }
    }
    ExampleService

    在Startup类里Configure中加入一个这个 endpoints.MapGrpcService<ExampleService>();   

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace GrpcGreeter
    {
        public class Startup
        {
            // This method gets called by the runtime. Use this method to add services to the container.
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddGrpc();
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseRouting();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGrpcService<GreeterService>();
                    endpoints.MapGrpcService<ExampleService>();
    
                    endpoints.MapGet("/", async context =>
                    {
                        await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                    });
                });
            }
        }
    }
    Startup

    就此 gRPC服务端代码完成了

      

    在 .NET 控制台应用中创建 gRPC 客户端

    • 打开 Visual Studio 的第二个实例并选择“创建新项目”。
    • 在“创建新项目”对话框中,选择“控制台应用(.NET Core)”,然后选择“下一步” 。
    • 在“项目名称”文本框中,输入“GrpcGreeterClient”,然后选择“创建” 。

    添加所需的包

    gRPC 客户端项目需要以下包:

    • Grpc.Net.Client,其中包含 .NET Core 客户端。
    • Google.Protobuf 包含适用于 C# 的 Protobuf 消息。
    • Grpc.Tools 包含适用于 Protobuf 文件的 C# 工具支持。 运行时不需要工具包,因此依赖项标记为 PrivateAssets="All"
    •  

    通过包管理器控制台 (PMC) 或管理 NuGet 包来安装包。

    用于安装包的 PMC 选项

    • 从 Visual Studio 中,依次选择“工具” > “NuGet 包管理器” > “包管理器控制台”

    • 从“包管理器控制台”窗口中,运行 cd GrpcGreeterClient 以将目录更改为包含 GrpcGreeterClient.csproj 文件的文件夹。

      运行以下命令:
      PowerShell

    Install-Package Grpc.Net.Client
    Install-Package Google.Protobuf
    Install-Package Grpc.Tools
    

      

     

    管理 NuGet 包选项以安装包

    • 右键单击“解决方案资源管理器” > “管理 NuGet 包”中的项目 。
    • 选择“浏览”选项卡。
    • 在搜索框中输入 Grpc.Net.Client。
    • 从“浏览”选项卡中选择“Grpc.Net.Client”包,然后选择“安装” 。
    • 为 Google.Protobuf 和 Grpc.Tools 重复这些步骤。

    添加 greet.proto

    • 在 gRPC 客户端项目中创建 Protos 文件夹。

    • 从 gRPC Greeter 服务将 Protosgreet.proto 文件复制到 gRPC 客户端项目。

    • 将 greet.proto 文件中的命名空间更新为项目的命名空间:

      option csharp_namespace = "GrpcGreeterClient";
      
    • 编辑 GrpcGreeterClient.csproj 项目文件:

      右键单击项目,并选择“编辑项目文件”。


    添加具有引用 greet.proto 文件的 <Protobuf> 元素的项组:

    XML
    <ItemGroup>
      <Protobuf Include="Protosgreet.proto" GrpcServices="Client" />
    </ItemGroup>
    

      

    创建 Greeter 客户端

    构建客户端项目,以在 GrpcGreeter 命名空间中创建类型。 GrpcGreeter 类型是由生成进程自动生成的。

    使用以下代码更新 gRPC 客户端的 Program.cs 文件:

    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Grpc.Net.Client;
    
    namespace GrpcGreeterClient
    {
        class Program
        {
            static async Task Main(string[] args)
            {
                // The port number(5001) must match the port of the gRPC server.
                using var channel = GrpcChannel.ForAddress("https://localhost:5001");
                var client =  new Greeter.GreeterClient(channel);
                var reply = await client.SayHelloAsync(
                                  new HelloRequest { Name = "GreeterClient" });
                Console.WriteLine("Greeting: " + reply.Message);
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }
    

    添加内容如下:

    <ItemGroup>
        <Protobuf Include="Protosexample.proto" GrpcServices="Server" />
        <Protobuf Include="Protosgreet.proto" GrpcServices="Server" />
      </ItemGroup>
      <ItemGroup>
        <Protobuf Include="Protosgreet.proto" GrpcServices="Client" />
      </ItemGroup>
      <ItemGroup>
        <Protobuf Include="Protosexample.proto" GrpcServices="Client" />
      </ItemGroup>
    

    在gRPC客户端写调用服务端代码,代码如下:

    using Grpc.Core;
    using Grpc.Net.Client;
    using GrpcGreeter;
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace GrpcGreeterClient
    {
        class Program
        {
            //static void Main(string[] args)
            //{
            //    Console.WriteLine("Hello World!");
            //}
            //static async Task Main(string[] args)
            //{
            //    // The port number(5001) must match the port of the gRPC server.
            //    using var channel = GrpcChannel.ForAddress("https://localhost:5001");
            //    var client = new Greeter.GreeterClient(channel);
            //    var reply = await client.SayHelloAsync(
            //                      new HelloRequest { Name = "GreeterClient" });
            //    Console.WriteLine("Greeting: " + reply.Message);
            //    Console.WriteLine("Press any key to exit...");
            //    Console.ReadKey();
            //}
    
    
            static async Task Main(string[] args)
            {
                // The port number(5001) must match the port of the gRPC server.
                using var channel = GrpcChannel.ForAddress("https://localhost:5001");
                var client = new exampler.examplerClient(channel);
    
                #region 一元调用
    
                //var reply = await client.UnaryCallAsync(new ExampleRequest { Id = 1, Name = "hda" });
                //Console.WriteLine("Greeting: " + reply.Msg);
    
                #endregion 一元调用
    
                #region  服务器流式处理调用
    
                //using var call = client.StreamingFromServer(new ExampleRequest { Id = 1, Name = "hda" });
    
                //while (await call.ResponseStream.MoveNext(CancellationToken.None))
                //{
                //    Console.WriteLine("Greeting: " + call.ResponseStream.Current.Msg);
    
                //}
                //如果使用 C# 8 或更高版本,则可使用 await foreach 语法来读取消息。 IAsyncStreamReader<T>.ReadAllAsync() 扩展方法读取响应数据流中的所有消息:
                //await foreach (var response in call.ResponseStream.ReadAllAsync())
                //{
                //    Console.WriteLine("Greeting: " + response.Msg);
                //    // "Greeting: Hello World" is written multiple times
                //}
    
                #endregion  服务器流式处理调用
    
                #region  客户端流式处理调用
                //using var call = client.StreamingFromClient();
                //for (int i = 0; i < 5; i++)
                //{
                //    await call.RequestStream.WriteAsync(new ExampleRequest { Id = i, Name = "hda" + i });
                //}
                //await call.RequestStream.CompleteAsync();
                //var response = await call;
                //Console.WriteLine($"Count: {response.Msg}");
                #endregion 客户端流式处理调用
    
                #region  双向流式处理调用
    
                //通过调用 EchoClient.Echo 启动新的双向流式调用。
                //使用 ResponseStream.ReadAllAsync() 创建用于从服务中读取消息的后台任务。
                //使用 RequestStream.WriteAsync 将消息发送到服务器。
                //使用 RequestStream.CompleteAsync() 通知服务器它已发送消息。
                //等待直到后台任务已读取所有传入消息。
                //双向流式处理调用期间,客户端和服务可在任何时间互相发送消息。 与双向调用交互的最佳客户端逻辑因服务逻辑而异。
                using var call = client.StreamingBothWays();
                Console.WriteLine("Starting background task to receive messages");
                var readTask = Task.Run(async () =>
                {
                    await foreach (var response in call.ResponseStream.ReadAllAsync())
                    {
                        Console.WriteLine(response.Msg);
                        // Echo messages sent to the service
                    }
                });
                Console.WriteLine("Starting to send messages");
                Console.WriteLine("Type a message to echo then press enter.");
                while (true)
                {
                    var result = Console.ReadLine();
                    if (string.IsNullOrEmpty(result))
                    {
                        break;
                    }
    
                    await call.RequestStream.WriteAsync(new ExampleRequest { Id=1,Name= result });
                }
    
                Console.WriteLine("Disconnecting");
                await call.RequestStream.CompleteAsync();
                await readTask;
                #endregion 双向流式处理调用
    
    
    
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }
    View Code

    代码链接地址: https://files.cnblogs.com/files/hudean/GrpcGreeter.zip

  • 相关阅读:
    wp8 入门到精通 测量代码执行时间
    过滤器——Filter
    hisui培训笔记
    监听器——servlet
    easyui导出excel表格和遇到的问题
    Java自定义注解
    Json
    Ajax
    探索Java中new一个对象时发生了什么
    SpringBoot常用注解
  • 原文地址:https://www.cnblogs.com/hudean/p/14028957.html
Copyright © 2011-2022 走看看