zoukankan      html  css  js  c++  java
  • DotNet Core 中使用 gRPC

    gRPC 简介

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

    官网:https://grpc.io/

    Github:https://github.com/grpc/grpc

    DotNet Core 官方示例:https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/grpc

    gRPC 的主要优点
    • 现代高性能轻量级 RPC 框架。
    • 约定优先的 API 开发,默认使用 Protocol Buffers 作为描述语言,允许与语言无关的实现。
    • 可用于多种语言的工具,以生成强类型的服务器和客户端。
    • 支持双向流式的请求和响应,对批量处理、低延时场景友好。
    • 通过 Protocol Buffers 二进制序列化减少网络使用。
    • 使用 HTTP/2 进行传输
    gRPC 适用的场景
    • 高性能的轻量级微服务。
    • 多语言混合开发的 Polyglot 系统。
    • 需要处理流式处理请求或响应的点对点实时通信服务。
    gRPC 不适用的场景
    • 浏览器可访问的 API:浏览器不完全支持 gRPC。虽然 gRPC-Web 可以提供浏览器支持,但是它有局限性,引入了服务器代理。
    • 广播实时通信:gRPC 支持通过流进行实时通信,但不存在向已注册连接广播消息的概念。
    • 进程间通信:进程必须承载 HTTP/2 才能接受传入的 gRPC 调用,对于 Windows,进程间通信管道是一种更快速的方法。
    gRPC 支持的语言

    目前 gRPC 已经实现了对主流语言的支持,以下语言在 gRPC 的 Github 中都提供了实现。

    支持的语言

    在 DotNet Core 中使用 gRPC

    创建服务端

    Visual Studio 2019 中已经集成了 gRPC 项目的模版,我们可以通过这个模版快速的创建一个基于 DotNet Core 的 gRPC 项目。

    创建好的项目结构如下:

    这时候项目不用做任何修改就可以运行了,那么这个项目和普通的 DotNet Core 项目有什么不同呢?
    首先项目文件 GrpcService.csproj 中引入了 Grpc.AspNetCore 包。

      <ItemGroup>
        <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
      </ItemGroup>
    

    appsettings.json 文件中多出了一个 Kestrel 节点,配置 Protocols 使用 Http2 协议。

      "Kestrel": {
        "EndpointDefaults": {
          "Protocols": "Http2"
        }
      }
    

    服务端 GreeterService 类的实现如下:

        public class GreeterService : Greeter.GreeterBase
        {
            private readonly ILogger<GreeterService> _logger;
            public GreeterService(ILogger<GreeterService> logger)
            {
                _logger = logger;
            }
    
            public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
            {
                return Task.FromResult(new HelloReply
                {
                    Message = "Hello " + request.Name
                });
            }
        }
    

    服务端 Startup 类中注入了 gRPC 服务:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.UseRouting();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<GreeterService>();
    
                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");
                });
            });
        }
    

    gRPC 工具会根据 proto 文件自动生成需要使用的类,生成的类会存放在项目的 objDebug etcoreapp3.1 目录下:

    创建客户端

    客户端项目需要手动的创建,创建方法也很简单,直接在解决方案中添加一个新的项目即可,这里我创建了一个空的 Web 项目。

    项目创建好了以后首先要把 proto 文件添加到项目中,这里需要用到 dotnet-grpc 这个工具。
    在命令行下安装 gRPC 工具:

    dotnet tool install dotnet-grpc -g
    

    安装完以后从命令行进入 GrpcClient 项目的目录后,添加服务端的 proto 文件到客户端。

     dotnet grpc add-file ..GrpcServiceProtosgreet.proto 
    

    也可以使用远程路径的 proto 文件:

    dotnet grpc add-url 
    https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto -o /Protos/keyvaluesrore.proto
    

    导入 proto 文件以后,GrpcClient 项目文件中会增加如下代码:

      <ItemGroup>
        <Protobuf Include="..GrpcServiceProtosgreet.proto" 
    Link="Protosgreet.proto" />
        <Protobuf Include="XXXX/Protos/keyvaluesrore.proto" 
    Link="Protoskeyvaluesrore.proto">
    <SourceUrl>https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto</SourceUrl>
        </Protobuf>
      </ItemGroup>
    

    dotnet-grpc 除了以上用法还支持以下命令,详细用法可以查阅 Microsoft Docs

    • dotnet grpc add-file
    • dotnet grpc add-url
    • dotnet grpc remove
    • dotnet grpc refresh

    接下来修改 Startup 中的代码,在 ConfigureServices(IServiceCollection services) 方法注入 gRPC 客户端代码:

    services.AddGrpcClient<GreeterClient>(options => options.Address = new 
    Uri("https://localhost:5001"));
    

    注:Uri("https://localhost:5001") 中使用服务端的端口和地址。

    Configure 方法中添加响应代码:

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/", async context =>
            {
                GreeterClient client = context.RequestServices.GetService<GreeterClient>();
                HelloRequest request = new HelloRequest();
                request.Name = "Charles";
                var reply = await client.SayHelloAsync(request);
    
                await context.Response.WriteAsync(reply.Message);
            });
        });
    

    如果不想使用注入的方式也可以直接调用:

        var httpClientHandler = new HttpClientHandler();
        var httpClient = new HttpClient(httpClientHandler);
    
        var channel = GrpcChannel.ForAddress(Address);
        var client = new GreeterClient(channel);
    
        HelloRequest request = new HelloRequest
        {
            Name = "Charles"
        };
        var reply = await client.SayHelloAsync(request);
    
        await context.Response.WriteAsync(reply.Message);
    

    启动项目后在客户端可以看到输出了服务端返回的内容:

    Hello Charles
    
    限制消息大小

    消息大小限制是一种有助于防止 gRPC 消耗过多资源的机制。gRPC 使用每个消息的大小限制来管理传入和传出消息。 默认情况下,gRPC 将传入消息限制为 4 MB。 传出消息没有限制。
    在服务端上,可以使用 AddGrpc 为应用中的所有服务配置 gRPC 消息限制:

        services.AddGrpc(options =>
        {
            options.MaxReceiveMessageSize = 1 * 1024 * 1024; // 1 MB
            options.MaxSendMessageSize = 1 * 1024 * 1024; // 1 MB
        });
    
    调用不安全的 gRPC 服务:

    若要使客户端调用不安全的 gRPC 服务,需要修改客户端的配置。

        AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    调用不受信任、无效证书调用 gRPC 服务

    DotNet gRPC 客户端要求服务具有受信任的证书,若要调用不受信任、无效证书调用 gRPC 服务,需要修改客户端请求的代码:

        services.AddGrpcClient<GreeterClient>(options => options.Address = new Uri(Address)).
        ConfigurePrimaryHttpMessageHandler(provider =>
        {
            var handler = new SocketsHttpHandler();
            handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; // 允许不受信任、无效证书
            return handler;
        });
    

    非注入方式:

        var httpClientHandler = new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        };
    
        var httpClient = new HttpClient(httpClientHandler);
    
        var channel = GrpcChannel.ForAddress(Address);
        var client = new GreeterClient(channel);
    
        HelloRequest request = new HelloRequest
        {
            Name = "Charles"
        };
        var reply = await client.SayHelloAsync(request);
    

    身份验证和授权

    服务端

    为服务端加入身份验证也很简单,首先需要为项目引入 Microsoft.AspNetCore.Authentication.JwtBearer 这个包。

        <ItemGroup>
            <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
            <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
        </ItemGroup>
    

    修改服务端 Startup 类的代码,分别在 ConfigureConfigureServices 方法中加入如下代码:

        services.AddAuthorization(options =>
        {
            options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
            {
                policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
                policy.RequireClaim(ClaimTypes.Name);
            });
        });
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters =
                    new TokenValidationParameters
                    {
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ValidateActor = false,
                        ValidateLifetime = true,
                        IssuerSigningKey = SecurityKey
                    };
            });
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGrpcService<GreeterService>();
    
            endpoints.MapGet("/getToken", context =>
            {
                return context.Response.WriteAsync(GenerateJwtToken(context.Request.Query["name"]));
            });
        });
    

    生成 Token 的方法:

        private string GenerateJwtToken(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new InvalidOperationException("Name is not specified.");
            }
    
            var claims = new[] { new Claim(ClaimTypes.Name, name) };
            var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken("JwtSecurityIssuer", "JwtSecurityClients", claims, expires: DateTime.Now.AddSeconds(60), signingCredentials: credentials);
            return JwtTokenHandler.WriteToken(token);
        }
    
    客户端

    客户端的实现逻辑是在请求服务端之前先从服务端获取到 Token,在之后调用响应服务的时候将 Token 放入请求头中传入服务端,如果不传入直接请求服务端会返回 401。

        var httpClient = new HttpClient();
        var httpRequest = new HttpRequestMessage
        {
            RequestUri = new Uri($"{Address}/getToken?name=Charles"),
            Method = HttpMethod.Get,
            Version = new Version(2, 0)
        };
        var tokenResponse = await httpClient.SendAsync(httpRequest);
        tokenResponse.EnsureSuccessStatusCode();
    
        var token = await tokenResponse.Content.ReadAsStringAsync();
        Metadata headers = null;
        if (token != null)
        {
            headers = new Metadata
            {
                { "Authorization", $"Bearer {token}" }
            };
        }
    
        var channel = GrpcChannel.ForAddress(Address);
        var client = new GreeterClient(channel);
    
        HelloRequest request = new HelloRequest
        {
            Name = "Charles"
        };
        var reply = await client.SayHelloAsync(request, headers);
    

    总结

    以上就是在 DotNet Core 中使用 gRPC 的常用方法了,想深入学习可以去查看 gRPC DotNet 项目的 Github,里面有很多实例可以参考:https://github.com/grpc/grpc-dotnet/tree/master/examples

  • 相关阅读:
    wikiquote
    zz 勵志貼,成功是努力加对的方向
    # 电纸书
    # 崔寶秋
    好的程序員
    深度学习引擎
    再见乱码:5分钟读懂MySQL字符集设置
    Linux基础:用tcpdump抓包
    Linux基础:文件查找find
    Linux基础:xargs命令
  • 原文地址:https://www.cnblogs.com/weisenz/p/dotnet-core-grpc.html
Copyright © 2011-2022 走看看