玩转gRPC和.NET 6:客户端

146 阅读5分钟

玩转gRPC和.NET 6:客户端

在.NET中创建一个gRPC客户端。

在我之前的文章中,我着重介绍了如何使用.NET创建一个gRPC服务器。现在,我想告诉你我们如何使用.NET创建一个gRPC客户端。

在下一张图片中,我标记了本教程的重点项目。

前提条件

我使用的是macOS,接下来的命令是针对这个操作系统的。

  • gRPC编译器。要安装Protobuf编译器,你可以在终端中执行这个。
brew install protobuf
  • .NET 6 SDK。在这里你可以找到下载和安装.NET 6 SDK的链接。
  • Visual Studio Code或你选择的IDE
  • grpcurl。一个提供与gRPC服务互动的命令行工具
brew install grpcurl
  • grpcui:建立在gRPCurl之上,为gRPC增加了一个交互式的Web UI,类似于Postman和Swagger UI等工具。
brew install grpcui

操作步骤

在一般情况下,我们应该遵循以下步骤。

  1. 创建一个项目。
  2. 在项目中添加gRPC的依赖项。
  3. 在你的项目中添加proto文件。
  4. 注册创建的proto文件并编译项目。
  5. 实现业务逻辑。

在这个例子中,我们将使用一个名为CountryGrpcClient 的.NET项目,调用服务器来搜索、创建或获得一个国家的列表。 CountryGrpc.proto 文件(如下所述)声明了远程程序。

1. 创建一个项目

要创建一个gRPC模板.NET项目,请打开一个终端,执行下一个命令。

dotnet new console -o grpc.country.client -n CountryGrpcClient

输出结果是这样的。

这里。

  • -o 参数用于定义项目的目录名称: 。grpc.country.client
  • -n 参数用于定义项目名称: 。CountryGrpcClient

2. 将gRPC依赖项添加到项目中

dotnet add CountryGrpcClient.csproj package Grpc.Net.Client --version '2.47.0'
dotnet add CountryGrpcClient.csproj package Grpc.Tools --version '2.47.0'
dotnet add CountryGrpcClient.csproj package Google.Protobuf --version '3.21.5'

你可以看到,你的CountryGrpcClient.csproj 中的条目ItemGroup 已经变成了这样的内容。

3. 在项目中添加原型文件

在你的项目目录中,创建一个名为Protos 的文件夹,里面有一个文件CountryGrpc.protos

mkdir ./Protos
touch ./Protos/CountryGrpc.proto

在这一步,我将使用在上一篇文章中创建的同样的proto文件,在这篇文章中我们创建了一个gRPC服务器。

在之前创建的CountryGrpc.proto 文件中复制接下来的几行。

ProtoBuf

syntax = "proto3";

/*The Proto file that has Empty message definition*/
import "google/protobuf/empty.proto";

// Defining the namespace in which the generate classes will be 
option csharp_namespace = "Sumaris.Grpc.Services";

// The service name will be used by the compiler when generate the base classes
// Here I declare five procedure
service CountryService{
	//Server streaming RPC
    rpc getAllCountries(google.protobuf.Empty)
        returns (stream Country);
	// Unitary RPC
    rpc listAllCountries(google.protobuf.Empty)
        returns ( CountryList);
    // Unitary RPC
    rpc findCountryByName( FindCountryByNameRequest )
        returns (FindCountryByNameResponse);
	// Unitary RPC
    rpc createCountry (CountryCreateRequest)
        returns (CountryCreateRespopnse);
	// Bidrectional streaming RPC
    rpc findCountriesByNames( stream FindCountryByNameRequest)
        returns (stream Country);
}


message Country{
    string name=1;
    string capitalCity=2;
    float area=3;
}

message CountryList{repeated Country country = 1;}

message FindCountryByNameRequest{string name=1;}

message FindCountryByNameResponse{Country country=1;}

message CountryCreateRequest{ Country country=1;}

message CountryCreateRespopnse{bool created=1;}

4. 注册创建的Proto文件并编译项目

在你的配置项目文件中添加以下几行,CountryGrpcClient.csproj

XML

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

打开终端,进入项目,并执行下一条命令来编译项目。

dotnet build

构建过程会创建两个文件,CountryGrpc.csCountryGrpcGrpc.cs , 在 obj/Debug/net6.0/Protos 路径中。 文件CountryGrpcGrpc.cs 包含我们将用作客户端与gRPC服务器交互的类。

5.实现业务逻辑

打开IDE,编辑Program.cs 文件,添加调用服务器的代码。

C#

using System.Threading.Tasks;
using Grpc.Net.Client;
using Sumaris.Grpc.Services;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;


var countryNames = new string[] { "Perú", "Ecuador", "Chile", "Brasil", "Argentina", "Venezuela" };

// See https://aka.ms/new-console-template for more information
using var channel =
    GrpcChannel.ForAddress("http://localhost:5000");
CountryService.CountryServiceClient? client =
    new CountryService.CountryServiceClient(channel);

// Consume unitary
// rpc listAllCountries(google.protobuf.Empty) returns ( CountryList);
var reply = await client.listAllCountriesAsync(new Empty());
Console.WriteLine("Hello, World!");
Console.WriteLine("Greeting: " + reply.Country);


// Consume stream server
//rpc getAllCountries(google.protobuf.Empty) returns (stream Country);
printServerStream();


// Consume unitary
//rpc findCountryByName(FindCountryByNameRequest ) returns(FindCountryByNameResponse);


// Consume client streaming server streaming
//rpc findCountriesByNames( stream FindCountryByNameRequest)  returns (stream Country);
var asyncDuplexStreamingCall = client.findCountriesByNames();
callStreamFromClient(asyncDuplexStreamingCall.RequestStream);
getStreamFromServer(asyncDuplexStreamingCall.ResponseStream);


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



async void printServerStream()
{
    var reply2 = client.getAllCountries(new Empty()).ResponseStream;
    Console.WriteLine("Greeting2: ");
    while (await reply2.MoveNext())
    {
        Console.Write(reply2.Current);
    }
    Console.WriteLine();
}

async void callStreamFromClient(IClientStreamWriter<FindCountryByNameRequest> request)
{
    foreach (var countryName in countryNames)
    {
        var d = new FindCountryByNameRequest(); 
        d.Name = countryName;
        await request.WriteAsync(d);
    }
    await request.CompleteAsync();
}

async void getStreamFromServer(IAsyncStreamReader<Country> response)
{
    await foreach(var p in response.ReadAllAsync())
    {
        Console.Write($">>{response.Current}");
    }

    Console.WriteLine("Terminando ...");
}

在接下来的图片中,我想再解释一下你是如何消费远程程序的。

标记为1的步骤让你创建*"客户端*"对象。你只需要一个客户端来与服务器交互。

从这一点出发,你用*"客户端 "*来调用每一个远程过程,正如你在步骤2中看到的那样。在这种情况下,客户端正在调用一个名为getAllCountries 的服务器流式RPC。服务器以异步和流式的方式将数据发送到客户端(第3步)。客户端也以流式方式读取数据,直到服务器完成发送(第4步)。

现在我们看到了如何调用一个流式服务器gRPC,我将告诉你如何调用一个双向流式gRPC

在这种情况下,我们使用之前创建的相同的*"客户端*"对象,即步骤1,并调用远程双向流媒体gRPC。在这个例子中,它是findCountriesByName ,它返回一个对象AsyncServerStreamingCall (步骤2)*。*这个对象包裹了两个对象:用于向服务器发送流媒体数据的RequestStream (步骤3),以及用于获取服务器返回的流媒体数据的ResponseStream (步骤5)。

服务器处理客户端在传输流中发送的每个传入对象,应用其业务逻辑,并异步地将其响应写到传输返回流中,正如我们在步骤4中看到的。客户端使用IAsyncStreamReader 读取服务器发送的传入响应,正如你在步骤5中看到的。

当客户端没有更多的数据要发送给服务器时,它必须通知服务器(步骤3中的红框),这样服务器就完成了异步读取请求的过程,可以在步骤4中离开foreach 。在这种情况下,服务器结束其进程,并通知客户端没有更多的数据需要读取。这时,客户端在步骤5中退出foreach

这样就完成了双向传输。

现在我已经向你展示了一个双向流式gRPC,你可以用它来实现一个客户端流式gRPC。

结论

我们使用协议缓冲区文件来生成.NET中的客户端实现,并使用async/await来管理流式服务器或客户端。 我们看到了如何调用一个流媒体服务器和双向流媒体gRPC。

如果你有任何问题或反馈,请随时告诉我。

谢谢你!