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 文件自動生成需要使用的類,生成的類會存放在項目的 obj\Debug\netcoreapp3.1 目錄下:

創建客戶端

客戶端項目需要手動的創建,創建方法也很簡單,直接在解決方案中添加一個新的項目即可,這里我創建了一個空的 Web 項目。

項目創建好了以后首先要把 proto 文件添加到項目中,這里需要用到 dotnet-grpc 這個工具。
在命令行下安裝 gRPC 工具:

dotnet tool install dotnet-grpc -g

安裝完以后從命令行進入 GrpcClient 項目的目錄后,添加服務端的 proto 文件到客戶端。

 dotnet grpc add-file ..\GrpcService\Protos\greet.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="..\GrpcService\Protos\greet.proto" 
Link="Protos\greet.proto" />
    <Protobuf Include="XXXX/Protos/keyvaluesrore.proto" 
Link="Protos\keyvaluesrore.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM