.NET gRPC 核心功能初體驗,附Demo源碼


gRPC是高性能的RPC框架, 有效地用於服務通信(不管是數據中心內部還是跨數據中心)。

由Google開源,目前是一個Cloud Native Computing Foundation(CNCF)孵化項目。

其功能包括:

  • 雙向流
  • 強大的二進制序列化
  • 可插拔的身份驗證,負載平衡和運行狀況檢查

在gRPC中,客戶端應用程序可以直接在A服務器上調用B服務器的方法,就好像它是本地對象一樣,從而使您更輕松地創建分布式應用程序和微服務。

與許多RPC系統一樣,gRPC也是圍繞着定義服務的思想(定義可遠程調用方法的入參和返回值類型)。

在服務器端,服務器實現此接口並運行gRPC服務器,以處理客戶端調用。
在客戶端,客戶端具有一個存根(在某些語言中僅稱為客戶端),提供與服務器相同的方法。

在本文中,我將向您展示如何使用.NET5創建gRPC服務。我將分解gRPC的一些重要基礎概念,並給出一個通信示例。

1.創建一個gRPC服務器

我們將從使用gRPC服務模板創建一個新的dotnet項目。

VS gRPC服務模板默認使用TLS 來創建gRRPC服務, 實際上不管是HTTP1.1 還是HTTP2, 都不強制要求使用TLS, (gRpc基於HTTP2)
如果服務一開始同時支持HTTP1.1+ HTTP2 但是沒有TLS, 那么協商的結果將是 HTTP1.1+ TLS,這樣的話gRPC調用將會失敗。

如果使用Visual Studio,請創建一個新項目,然后選擇gRPC Service模板,使用GrpcAuthor作為項目的名稱。

1.1 The RPC Service Definition

客戶端與服務端使用protocol buffers交流/通信:
protocol buffers既用作服務的接口定義語言(IDL),又用作底層消息交換格式

① 使用protocol buffers在.proto文件中定義服務接口。在其中,定義可遠程調用的方法的入參和返回值類型。服務器實現此接口並運行gRPC服務器以處理客戶端調用。
② 定義服務后,使用protocol buffers編譯器protoc從.proto文件生成數據訪問/傳輸類。該文件包含服務接口中消息和方法的實現。

關注VS腳手架項目Protos文件夾中的greet.proto。

syntax = "proto3";

option csharp_namespace = "GrpcAuthor";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

讓我們分解一下.proto文件,了解protocol buffers的基本語法

從.proto文件上大致知道 定義的服務功能 (給某人一個回應), 這里提示一些語法:

①. syntax指示使用的protocol buffers的版本。在這種情況下,proto3是撰寫本文時的最新版本。

②. csharp_namespace指示生成的文件所在的命名空間package說明符也是這個作用,用於防止協議消息類型之間的名稱沖突。

對於C#,如果提供選項csharp_namespace,csharp_namespace值將用作命名空間;
在Java中,如果提供選項java_package,java_package將用作包名稱。

③. service Greeter定義服務基類名稱, rpc SayHello (HelloRequest) returns (HelloReply); 是一個一元rpc調用

④. HelloRequestHelloReply是在客戶端和服務器之間交換信息的數據結構。它們被稱為消息
你在消息字段中定義的數字是不可重復的,當消息被序列化為Protobuf時,該數字用於標識字段,這是因為序列化一個數字比序列化整個字段名稱要快。

1.2 實現服務接口

為了從.proto文件生成代碼,可以使用protoc編譯器和C#插件來生成服務器或客戶端代碼。
腳手架項目使用Grpc.AspNetCore NuGet包:所需的類由構建過程自動生成, 你只需要在項目.csproj文件中添加 配置節:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

生成的代碼知道如何使用protocol buffers與其他服務/客戶端進行通信。

C#工具生成GreeterBase類型,將用作實現gRPC服務的基類。

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
        });
    }
}

最后注意注冊Grpc端點endpoints.MapGrpcService<GreeterService >();

--- 啟動服務---...

2. 創建gRPC .NET控制台客戶端

Visual Studio創建一個名為GrpcAuthorClient的新控制台項目。

安裝如下nuget包:
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools

Grpc.Net.Client包含.NET Core客戶端;
Google.Protobuf包含protobuf消息API;
Grpc.Tools對Protobuf文件進行編譯。

① 拷貝服務端項目中的..proto文件

② 將選項csharp_namespace值修改為GrpcAuthorClient。

③ 更新.csproj文件的 配置節

<ItemGroup>
 <Protobuf Include="Protos\author.proto" GrpcServices="Client" />
</ItemGroup>

④ Client主文件:

static void Main(string[] args)
       {
           var serverAddress = "https://localhost:5001";

           using var channel = GrpcChannel.ForAddress(serverAddress);
           var client = new Greeter.GreeterClient(channel);
           var reply = client.SayHello(new HelloRequest { Name = "宋小寶!" });

           Console.WriteLine(reply.Message.ToString());

           Console.WriteLine("Press any key to exit...");
           Console.ReadKey();
       }

使用服務器地址創建GrpcChannel,然后使用GrpcChannel對象實例化GreeterClient;然后使用SayHello同步方法; 服務器響應時,打印結果。

腳手架例子就可以入門,下面聊一聊另外的核心功能

3. 其他核心功能

3.1 通信方式

  • Unary RPC(一元Rpc調用): 上面的例子
  • Server streaming RPC : 服務器流式RPC,客戶端在其中向服務器發送請求,並獲取流以讀取回一系列消息。客戶端從返回的流中讀取,直到沒有更多消息為止。 gRPC保證單個RPC調用中的消息順序。
  • Client streaming RPC:客戶端流式RPC,客戶端在其中編寫一系列消息,然后再次使用提供的流將它們發送到服務器。客戶端寫完消息后,它將等待服務器讀取消息並返回響應。同樣,gRPC保證了單個RPC調用中的消息順序
  • Bidirectional streaming RPC: 雙向流式RPC,雙方都使用讀寫流發送一系列消息。這兩個流是獨立運行的,因此客戶端和服務器可以按照自己喜歡的順序進行讀寫:例如,服務器可以在寫響應之前等待接收所有客戶端消息,或者可以先讀取一條消息再寫入一條消息,或讀寫的其他組合。每個流中的消息順序都會保留。

gRPC保證 客戶端以與服務器發送消息相同的順序接收消息。

3.2 Metadata

元數據是以鍵值對列表的形式提供的有關特定RPC調用的信息(例如身份驗證詳細信息),其中鍵是字符串,值通常是字符串,但可以是二進制數據。

元數據對於gRPC本身是不透明的:它允許客戶端向服務器提供與調用相關的信息,反之亦然。

3.3 Channels

gRPC通道提供到指定主機和端口上的gRPC服務器的連接。
創建客戶端存根時用到它,可以指定通道參數來修改gRPC的默認行為,例如打開或關閉消息壓縮。
通道具有狀態,包括已連接和空閑。


4 gRPC打乒乓球

針對腳手架項目,稍作修改--->乒乓球局

(考察gRpc雙向流式通信、Timeout機制、異常處理):

客戶端發送"gridsum", 服務端回發"musdirg"; 客戶端再發送"gridsum", 往復......

① 添加接口

rpc PingPongHello(stream HelloRequest) returns (stream HelloReply);

② 實現服務契約

 public override async Task PingPongHello(IAsyncStreamReader<HelloRequest> requestStream,IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
        {
            try
            {
                while (!context.CancellationToken.IsCancellationRequested)
                {
                    var asyncRequests = requestStream.ReadAllAsync();
                    // 客戶端與服務端"打乒乓"
                    await foreach (var req in asyncRequests)
                    {
                        var send = Reverse(req.Name);
                        await responseStream.WriteAsync(new HelloReply
                        {
                            Message = send,
                            Id = req.Id + 1
                        });
                        Debug.WriteLine($"第{req.Id}回合,服務端收到 {req.Name};開始第{req.Id + 1}回合,服務端回發 {send}");
                    }
                }
            }
            catch (RpcException ex)
            {
                System.Diagnostics.Debug.WriteLine($"{ex.Message}");
            }
            catch (IOException ex)
            {
                System.Diagnostics.Debug.WriteLine($"{ex.Message}");
            }
        }

③ 添加客戶端代碼,控制5s終斷連接

using (var cancellationTokenSource = new CancellationTokenSource( 5* 1000))
{
     try
     {
           var duplexMessage = client.PingPongHello(null, null, cancellationTokenSource.Token);
           await duplexMessage.RequestStream.WriteAsync(new HelloRequest { Id = 1, Name = "gridsum" }) ;

           var asyncResp = duplexMessage.ResponseStream.ReadAllAsync();
           await foreach (var resp in asyncResp)
           {
              var send = Reverse(resp.Message);
              await duplexMessage.RequestStream.WriteAsync(new HelloRequest {Id= resp.Id, Name = send });
             Console.WriteLine($"第{resp.Id}回合,客戶端收到 {resp.Message}, 客戶端發送{send}");
          }
     }
     catch (RpcException ex)
     {
            Console.WriteLine("打乒乓球時間到了(客戶端5s后終斷gRpc連接)");
     }
}

https://github.com/zaozaoniao/GrpcAuthor

總結

gRPC是具有可插拔身份驗證和負載平衡功能的高性能RPC框架。
使用protocol buffers定義結構化數據;使用不同語言自動產生的源代碼在各種數據流中寫入和讀取結構化數據。

在本文中,您學習了如何使用protocol buffers(版本3)定義服務接口以及如何使用C#實現服務。最后,您使用gRPC雙向流式通信創建了 "打乒乓球"Demo。

Additional Resources

https://developers.google.com/protocol-buffers/docs/csharptutorial
https://www.grpc.io/docs/what-is-grpc/core-concepts/
https://docs.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/why-grpc


免責聲明!

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



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