Condividi tramite


Chiamare servizi gRPC con il client .NET

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Una libreria client .NET gRPC è disponibile nel pacchetto NuGet Grpc.Net.Client . Questo documento illustra come:

  • Configurare un client gRPC per chiamare i servizi gRPC.
  • Effettuare chiamate gRPC a metodi unari, streaming server, streaming client e streaming bidirezionale.

Configurare il client gRPC

I client gRPC sono tipi client concreti generati da file .proto. Il client gRPC concreto ha metodi che si riferiscono al servizio gRPC nel file .proto. Ad esempio, un servizio denominato Greeter genera un GreeterClient tipo con metodi per chiamare il servizio.

Un client gRPC viene creato da un canale. Iniziare usando GrpcChannel.ForAddress per creare un canale e quindi usare il canale per creare un client gRPC:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);

Un canale rappresenta una connessione di lunga durata a un servizio gRPC. Quando viene creato un canale, viene configurato con le opzioni correlate alla chiamata di un servizio. Ad esempio, l'oggetto HttpClient usato per effettuare chiamate, le dimensioni massime di invio e ricezione dei messaggi e la registrazione possono essere specificate GrpcChannelOptions e usate con GrpcChannel.ForAddress. Per un elenco completo delle opzioni, vedere Opzioni di configurazione client.

var channel = GrpcChannel.ForAddress("https://localhost:5001");

var greeterClient = new Greet.GreeterClient(channel);
var counterClient = new Count.CounterClient(channel);

// Use clients to call gRPC services

Configurare TLS

Un client gRPC deve usare la stessa sicurezza a livello di connessione del servizio chiamato. Il client gRPC Transport Layer Security (TLS) viene configurato quando viene creato il canale gRPC. Un client gRPC genera un errore quando chiama un servizio e la sicurezza a livello di connessione del canale e del servizio non corrispondono.

Per configurare un canale gRPC per l'uso di TLS, assicurarsi che l'indirizzo del server inizi con https. Ad esempio, GrpcChannel.ForAddress("https://localhost:5001") usa il protocollo HTTPS. Il canale gRPC negozia automaticamente una connessione protetta da TLS e usa una connessione sicura per effettuare chiamate gRPC.

Suggerimento

gRPC supporta l'autenticazione del certificato client tramite TLS. Per informazioni sulla configurazione dei certificati client con un canale gRPC, vedere Autenticazione e autorizzazione in gRPC per ASP.NET Core.

Per chiamare i servizi gRPC non protetti, verificare che l'indirizzo del server inizi con http. Ad esempio, GrpcChannel.ForAddress("http://localhost:5000") usa il protocollo HTTP. In .NET Core 3.1 è necessaria una configurazione aggiuntiva per chiamare servizi gRPC non sicuri con il client .NET.

Prestazioni client

Prestazioni e utilizzo di canale e client:

  • La creazione di un canale può essere un'operazione costosa. Il riutilizzo di un canale per le chiamate gRPC offre vantaggi in termini di prestazioni.
  • Un canale gestisce le connessioni al server. Se la connessione viene chiusa o persa, il canale si riconnette automaticamente alla successiva chiamata gRPC.
  • I client gRPC vengono creati con i canali. I client gRPC sono oggetti leggeri e non devono essere memorizzati nella cache o riutilizzati.
  • È possibile creare più client gRPC da un canale, inclusi diversi tipi di client.
  • Un canale e i client creati dal canale possono essere usati in modo sicuro da più thread.
  • I client creati dal canale possono effettuare più chiamate simultanee.

GrpcChannel.ForAddress non è l'unica opzione per la creazione di un client gRPC. Se si chiamano i servizi gRPC da un'app ASP.NET Core, prendere in considerazione l'integrazione della factory client gRPC. L'integrazione di gRPC con HttpClientFactory offre un'alternativa centralizzata alla creazione di client gRPC.

Quando si chiamano i metodi gRPC, preferire l'uso di programmazione asincrona con async e await. L'esecuzione di chiamate gRPC con blocco, ad esempio l'uso di Task.Result o Task.Wait(), impedisce ad altre attività di usare un thread. Ciò può causare l'esaurimento del pool di thread e scarse prestazioni. L'app potrebbe bloccarsi con un deadlock. Per altre informazioni, vedere Chiamate asincrone nelle app client.

Effettuare chiamate gRPC

Una chiamata gRPC viene avviata chiamando un metodo sul client. Il client gRPC gestirà la serializzazione dei messaggi e indirizza la chiamata gRPC al servizio corretto.

gRPC ha diversi tipi di metodi. Il modo in cui il client viene usato per effettuare una chiamata gRPC dipende dal tipo di metodo chiamato. I tipi di metodo gRPC sono:

  • Unario
  • Streaming del server
  • Client per lo streaming
  • Streaming bidirezionale

Chiamata unaria

Una chiamata unaria inizia con il client che invia un messaggio di richiesta. Al termine del servizio viene restituito un messaggio di risposta.

var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World

Ogni metodo di servizio unario nel .proto file genererà due metodi .NET sul tipo di client gRPC concreto per chiamare il metodo: un metodo asincrono e un metodo di blocco. Ad esempio, in GreeterClient esistono due modi per chiamare SayHello:

  • GreeterClient.SayHelloAsync: chiama il servizio Greeter.SayHello in modo asincrono. Può essere atteso con pazienza.
  • GreeterClient.SayHello: chiama il servizio Greeter.SayHello e blocca fino al completamento. Non usare nel codice asincrono. Può causare problemi di prestazioni e affidabilità.

Per ulteriori informazioni, vedere Chiamate asincrone nelle applicazioni client.

Chiamata di streaming del server

Una chiamata di streaming del server inizia con il client che invia un messaggio di richiesta. ResponseStream.MoveNext() legge i messaggi trasmessi dal servizio. La chiamata di streaming del server è completa quando ResponseStream.MoveNext() restituisce false.

var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });

while (await call.ResponseStream.MoveNext())
{
    Console.WriteLine("Greeting: " + call.ResponseStream.Current.Message);
    // "Greeting: Hello World" is written multiple times
}

Quando si usa C# 8 o versione successiva, la await foreach sintassi può essere usata per leggere i messaggi. Il IAsyncStreamReader<T>.ReadAllAsync() metodo di estensione legge tutti i messaggi dal flusso di risposta:

var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });

await foreach (var response in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine("Greeting: " + response.Message);
    // "Greeting: Hello World" is written multiple times
}

Il tipo restituito dall'avvio di una chiamata di streaming del server implementa IDisposable. Assicurarsi sempre di terminare una chiamata di streaming per fare in modo che venga arrestata e che tutte le risorse vengano pulite.

Chiamata di streaming del client

Viene avviata una chiamata di streaming client senza che il client invii un messaggio. Il client può scegliere di inviare messaggi con RequestStream.WriteAsync. Quando il client ha terminato di inviare i messaggi, si dovrebbe chiamare RequestStream.CompleteAsync() per notificare il servizio. La chiamata viene completata quando il servizio restituisce un messaggio di risposta.

var client = new Counter.CounterClient(channel);
using var call = client.AccumulateCount();

for (var i = 0; i < 3; i++)
{
    await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
await call.RequestStream.CompleteAsync();

var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3

Il tipo restituito dall'avvio di una chiamata di streaming client implementa IDisposable. Termina sempre una chiamata in streaming per assicurarti che sia terminata e che tutte le risorse siano pulite.

Chiamata di streaming bidirezionale

Una chiamata di streaming bidirezionale viene avviata senza che il client invii un messaggio. Il client può scegliere di inviare messaggi con RequestStream.WriteAsync. I messaggi trasmessi dal servizio sono accessibili con ResponseStream.MoveNext() o ResponseStream.ReadAllAsync(). La chiamata di streaming bidirezionale si completa quando ResponseStream non ha più messaggi.

var client = new Echo.EchoClient(channel);
using var call = client.Echo();

Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
    await foreach (var response in call.ResponseStream.ReadAllAsync())
    {
        Console.WriteLine(response.Message);
        // 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 EchoMessage { Message = result });
}

Console.WriteLine("Disconnecting");
await call.RequestStream.CompleteAsync();
await readTask;

Per ottenere prestazioni ottimali e per evitare errori non necessari nel client e nel servizio, provare a completare correttamente le chiamate di streaming bidirezionali. Una chiamata bidirezionale viene completata normalmente al termine della lettura del flusso di richiesta da parte del server e il client ha terminato di leggere il flusso di risposta. La chiamata di esempio precedente è un esempio di una chiamata bidirezionale che termina normalmente. Nella telefonata, il cliente:

  1. Avvia una nuova chiamata di streaming bidirezionale chiamando EchoClient.Echo.
  2. Crea un'attività in background per leggere i messaggi dal servizio usando ResponseStream.ReadAllAsync().
  3. Invia messaggi al server con RequestStream.WriteAsync.
  4. Notifica al server che ha terminato l'invio di messaggi con RequestStream.CompleteAsync().
  5. Attende che l'attività in background abbia letto tutti i messaggi in arrivo.

Durante una chiamata di streaming bidirezionale, il client e il servizio possono inviare messaggi l'uno all'altro in qualsiasi momento. La logica client migliore per interagire con una chiamata bidirezionale varia a seconda della logica del servizio.

Il tipo restituito dall'avvio di una chiamata di streaming bidirezionale implementa IDisposable. Assicurarsi sempre di gestire correttamente una chiamata di streaming per garantire che venga arrestata e che tutte le risorse vengano liberate.

Accedere alle intestazioni gRPC

Le chiamate gRPC restituiscono intestazioni di risposta. Le intestazioni di risposta HTTP trasmettono metadati sotto forma di coppie nome/valore relativi a una chiamata che non sono correlati al messaggio restituito.

Le intestazioni sono accessibili tramite ResponseHeadersAsync, che restituisce una raccolta di metadati. Le intestazioni vengono in genere restituite con il messaggio di risposta; quindi, è necessario attenderle.

var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });

var headers = await call.ResponseHeadersAsync;
var myValue = headers.GetValue("my-trailer-name");

var response = await call.ResponseAsync;

ResponseHeadersAsync uso:

  • Deve attendere il risultato di ResponseHeadersAsync per ottenere la collezione di intestazioni.
  • Non è necessario accedere prima ResponseAsync (o al flusso di risposta durante lo streaming). Se una risposta è stata restituita, allora ResponseHeadersAsync restituisce immediatamente le intestazioni.
  • Genererà un'eccezione nel caso in cui ci sia stato un errore di connessione o del server e le intestazioni non siano state restituite per la chiamata gRPC.

Accedere ai trailer gRPC

Le chiamate gRPC possono restituire trailer di risposta. I trailer vengono utilizzati per fornire metadati di nome/valore relativi a una chiamata. I trailer offrono funzionalità simili alle intestazioni HTTP, ma vengono ricevute alla fine della chiamata.

I trailer sono accessibili tramite GetTrailers(), che restituisce una raccolta di metadati. I trailer vengono restituiti una volta che la risposta è stata completata. Pertanto, è necessario attendere tutti i messaggi di risposta prima di accedere ai trailer.

Le chiamate unarie e di streaming del client devono attendere ResponseAsync prima di chiamare GetTrailers().

var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;

Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World

var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");

Le chiamate di streaming bidirezionali e del server devono terminare in attesa del flusso di risposta prima di chiamare GetTrailers():

var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });

await foreach (var response in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine("Greeting: " + response.Message);
    // "Greeting: Hello World" is written multiple times
}

var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");

I trailer sono accessibili anche da RpcException. Un servizio può restituire trailer insieme a uno stato gRPC non OK. In questo caso, i trailer vengono recuperati dall'eccezione generata dal client gRPC:

var client = new Greet.GreeterClient(channel);
string myValue = null;

try
{
    using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
    var response = await call.ResponseAsync;

    Console.WriteLine("Greeting: " + response.Message);
    // Greeting: Hello World

    var trailers = call.GetTrailers();
    myValue = trailers.GetValue("my-trailer-name");
}
catch (RpcException ex)
{
    var trailers = ex.Trailers;
    myValue = trailers.GetValue("my-trailer-name");
}

Configurare la scadenza

La configurazione di una scadenza di chiamata gRPC è consigliata perché fornisce un limite superiore per quanto tempo può essere eseguita una chiamata. Impedisce ai servizi malfunzionanti di continuare a funzionare indefinitamente ed esaurire le risorse del server. Le scadenze sono uno strumento utile per la creazione di app affidabili.

Configurare CallOptions.Deadline per impostare una scadenza per una chiamata gRPC:

var client = new Greet.GreeterClient(channel);

try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = "World" },
        deadline: DateTime.UtcNow.AddSeconds(5));
    
    // Greeting: Hello World
    Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
    Console.WriteLine("Greeting timeout.");
}

Per ulteriori informazioni, consultare Servizi gRPC affidabili con scadenze e cancellazione.

Risorse aggiuntive