• Noser.com
facebook
linkedin
twitter
youtube
  • NOSERmobile
    • Android
    • HTML 5
    • Hybrid Apps
    • iOS
    • Windows Phone
  • NOSERembedded
    • Medizintechnik
  • NOSERprojektmanagement
  • NOSERtesting
  • NOSERlifecycle
    • .NET Allgemein
    • Application Lifecylce Management
    • Apps
    • Architektur
    • ASP.NET
    • Azure
    • Cleancode
    • Cloud
    • Silverlight
    • Visual Studio / Team Foundation Server
    • Windows 8
    • Windows Presentation Foundation
  • NOSERinnovation
    • Big Data
    • Cloud
    • IoT
    • Operations Research
    • Augmented Reality
    • RFID, NFC, Bluetooth LE

gRPC Tutorial Teil 5: Client-Zertifikat Authentifizierung

07. Januar 2020
Erik Stroeken
4
Client-Zertifikat Authentifizierung, gRPC, Sicherheit

Wenn gRPC nur im Backend verwendet wird, bietet eine sichere Verbindung mit zusätzlicher Client-Zertifikat Authentifizierung meistens ausreichende Sicherheit. Mit dieser Form der Authentifizierung wird nicht nur die Identität des Servers, sondern auch diejenige des Clients bestätigt, um sicher zu gehen, dass sich kein Fremdprogramm mit dem Server verbindet. Dieses Tutorial zeigt Schritt für Schritt wie man den gRPC Server und Client einrichtet (mit selbstsignierten Zertifikaten) für Client-Zertifikat Authentifizierung.

Der Code zum Beispiel kann man hier herunterladen.

Client-Zertifikat Authentifizierung

Ein digitales Client-Zertifikat ist im Grunde eine Datei, die mit einem Passwort geschützt und in eine Client-Anwendung geladen wird (normalerweise als PKCS12-Dateien mit der Erweiterung ‘.p12’ oder ‘.pfx’).

Ein digitales Zertifikat enthält relevante Informationen wie eine digitale Signatur, Ablaufdatum, Name des Issuers, Name der CA (Certificate Authority), Sperrstatus (revocation), SSL/TLS-Versionsnummer, Seriennummer und möglicherweise weitere Informationen, die alle nach dem X.509-Standard strukturiert sind.

Ein Client-Zertifikat ist ein spezieller Zertifikat-Typ mit dem Ziel, seine Identität für einen anderen Rechner nachzuweisen, in unserem Fall also für den Server. Man erkennt den Zweck eines Zertifikates an Hand der OID (Object Identifier), welche eine Folge von Zahlen ist. Das Zertifikatfeld ‘Enhanced Key Usage’ eines Client-Zertifikates enthält die OID ‘1.3.6.1.5.5.7.3.2’.

Enhanced Key Usage for Client Certificate

Enhanced Key Usage for Client Certificate

Mit dem Suchwort ‘cert’ im Windows Startmenu erscheint ‘Manage computer certificates’, welches die Microsoft Management Konsole öffnet. Mit einem Doppelklick aufs Zertifikat erscheinen die detaillierten Informationen wie oben im Bild.

Zu Beginn einer SSL- oder TLS-Sitzung kann der Server (falls so konfiguriert) vom Client ein Client-Zertifikat verlangen. Nach dem Empfang des Zertifikats identifiziert der Server die Quelle des Zertifikats und entscheidet, ob dem Client der Zugriff erlaubt werden soll.

Selbstsignierte Zertifikate erstellen

Normalerweise werden Zertifikaten von Zertifizierungsstellen (‘certificate authority’ oder ‘certification authority’, kurz CA) ausgestellt (z.B. Verisign, Thawte, etc.). Für unser Beispiel werden wir selber die Zertifikate generieren. Es gibt verschiedene Wege selber Zertifikate zu generieren: Powershell, online Tools, OpenSSL, etc. In diesem Beitrag wird OpenSSL verwendet.

OpenSSL installieren

Die binäre Dateien des OpenSSL Tools stehen als ZIP-Datei zur Verfügung auf ‘https://sourceforge.net/projects/openssl‘ oder als Installer auf ‘https://slproweb.com/products/Win32OpenSSL.html‘. Lade die letzte Version des Installers ‘Win64OpenSSL_Light-1_1_0L.exe’ (oder höher) herunter und führe den Installer aus. Wichtig ist, dass ‘openssl.exe’ und ‘openssl.cnf’ sich nach der Installation im Verzeichnis ‘C:\OpenSSL-Win64\bin’ befinden.

Zertifikate generieren

Im Beispiel gibt es einen Unterordner ‘Certs’ in dem alle OpenSSL Tool Input- und Output-Dateien gespeichert werden.

Die Inhalte des Zertifikatfeldes ‘Enhanced Key Usage’ müssen für das OpenSSL Tool in einer externen Datei definiert werden. In diesem Verzeichnis eine Textdatei ‘openssl-ext.cnf’ mit folgendem Inhalt erstellen:

[client_ssl]
extendedKeyUsage = clientAuth

[server_ssl]
extendedKeyUsage = serverAuth

Eine zweite Datei namens ‘create_certs.cmd’ mit folgendem Inhalt erstellen:

set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg   

echo Generate CA key:
c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out root.key 4096

echo Generate CA certificate:
c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -x509 -days 3650 -key root.key -out root.crt -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=Noser RpcWithCertificates Root"

echo Generate server key:
c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out server.key 4096

echo Generate server signing request:
c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -key server.key -out server.csr -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=localhost"

echo Self-sign server certificate:
c:\OpenSSL-Win64\bin\openssl x509 -req -passin pass:P@ssw0rd -days 3650 -extensions server_ssl -extfile openssl-ext.cnf  -in server.csr -CA root.crt -CAkey root.key -set_serial 01 -out server.crt

echo Remove passphrase from server key:
c:\OpenSSL-Win64\bin\openssl rsa -passin pass:P@ssw0rd -in server.key -out server.key

echo Generate client key
c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out client.key 4096

echo Generate client signing request:
c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -key client.key -out client.csr -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=localhost"

echo Self-sign client certificate:
c:\OpenSSL-Win64\bin\openssl x509 -req -passin pass:P@ssw0rd -days 3650 -extensions client_ssl -extfile openssl-ext.cnf -in client.csr -CA root.crt -CAkey root.key -set_serial 01 -out client.crt

echo Remove passphrase from client key:
c:\OpenSSL-Win64\bin\openssl rsa -passin pass:P@ssw0rd -in client.key -out client.key

c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in root.crt -inkey root.key -out root.pfx -passout pass:P@ssw0rd -passin pass:P@ssw0rd -name "Noser Engineering self-signed root cert"

c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx -passout pass:P@ssw0rd -name "Noser Engineering self-signed server cert"

c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:P@ssw0rd -name "Noser Engineering self-signed client cert"

pause

Nach dem Ausführen des oberen Skripts hat das Verzeichnis folgenden Inhalt:

OpenSSL Ausgabe

OpenSSL Ausgabe

Das Skript generiert verschiedene (Zwischen-)Dateien, wovon für uns nur 3 wichtig sind:

  1. root.pfx (friendly name ‘Noser Engineering self-signed root cert’)
  2. server.pfx (friendly name ‘Noser Engineering self-signed server cert’)
  3. client.pfx (friendly name ‘Noser Engineering self-signed client cert’)

Die Datei ‘root.pfx’ enthält das Zertifikat mit ‘Intended Purposes All’ und wird zum Signieren des Server- und Client-Zertifikates verwendet. Das Server-Zertifikat hat ‘Intended Purposes Server Authentication’ und das Client-Zertifikat hat ‘Intended Purposes Client Authentication’. Das Server-Zertifikat wird später im gRPC Server geladen und das Client-Zertifikat im Client-Programm. Alle drei Zertifikate müssen aber zuerst auf dem Rechner installiert werden.

Installieren der Zertifikate

Da es nicht möglich ist mit OpenSSL den ‘friendly name’ in einer ‘crt’-Datei zu generieren, verwenden wir die ‘pfx’-Datei zum Installieren der Zertifikate.
Doppelklick auf root.pfx und wähle ‘Local Machine’.

Importieren Zertifikat

Importieren Zertifikat

Das Passwort einfügen (P@ssw0rd).

Importieren Zertifikat

Importieren Zertifikat: Passwort spezifizieren

Für das Root-Zertifikate den ‘Trusted Root Certification Authorities’ Store wählen.

Importieren Zertifikat: Store auswählen

Importieren Zertifikat: Trusted Root Store auswählen

Alle Schritte für ‘server.pfx’ und ‘client.pfx’ wiederholen mit dem Unterschied, dass ‘Intermediate Certification Authorities’ als Store gewählt werden soll.

Zertifikat Importieren: Intermediate Store auswählen

Zertifikat Importieren: Intermediate Store auswählen

Danach die Microsoft Management Konsole öffnen durch Eingabe von ‘cert’ im Windows Startmenü. Es erscheint ‘Manage computer certificates’ in den Ergebnissen, welches die Konsole öffnet.
Das Root-Zertifikat soll sich jetzt in ‘Trusted Root Certification Authorities’ befinden.

Root Zertifikat im Root Store

Root Zertifikat im Root Store

Und das Server- und Client-Zertifikat in ‘Intermediate Certification Authorities’.

Client- und Server-Zertifikat in intermediate Store

Client- und Server-Zertifikat in intermediate Store

gRPC Server Einrichten

Beim Server muss die Client-Zertifikat Authentifizierung aktiviert werden und das Server-Zertifikat installiert werden.

Als erste muss die Datei ‘server.pfx’ im Projekt kopiert werden und mit ins Ausgabeverzeichnis kopiert werden.

Inkludieren server.pfx Datei im Projekt

Inkludieren server.pfx Datei im Projekt

Passwort und Dateiname werden in appsettings.json in einer neuen Sektion ‘Certificate’ definiert:

{
    "Logging": {
…
    },
    "Certificate": {
        "File": "server.pfx",
        "Password": "P@ssw0rd"
    }
}

In der program.cs Datei wird der Kestrel-Server für die Sicherheitsvorkehrungen konfiguriert:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(
                opt =>
                {
                    var config = (IConfiguration)opt.ApplicationServices.GetService(typeof(IConfiguration));
                    var cert = new X509Certificate2(
                        config["Certificate:File"], config["Certificate:Password"]);
                    opt.ConfigureHttpsDefaults(
                    h =>
                    {
                        h.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
                        h.CheckCertificateRevocation = false;
                        h.ServerCertificate = cert;
                    });

                });
        });

Dazu wird ein X509-Zertifikat aus der ‘pfx’-Datei erstellt, zugewiesen und die Client-Zertifikat Authentifizierung eingeschaltet. Da selbstsignierte Zertifikate nicht widerrufen werden können, wird CheckCertificateRevocation ausgeschaltet.

In startup.cs muss der Authentication-Service hinzugefügt und konfiguriert werden.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(
            opt =>
            {
                opt.AllowedCertificateTypes = CertificateTypes.All;
                opt.RevocationMode = X509RevocationMode.NoCheck; // Just for development
            });
    services.AddAuthorization();
    services.AddControllers();
    services.AddGrpc(opt => { opt.EnableDetailedErrors = true; });
}

Als letzter Schritt muss der Zugriff auf den gRPC Service mit dem ‘Authorize’-Attribute verriegelt werden.

[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults.AuthenticationScheme)]
public class DemoServiceRpc : DemoService.DemoServiceBase
{
    public override Task<NameMessage> ExchangeNames(NameMessage request, ServerCallContext context)
    {
        NameMessage result = new NameMessage
        {
            Name = $"Hello {request.Name} from {Assembly.GetExecutingAssembly().FullName}"
        };
        return Task.FromResult(result);
    }
}

gRPC Client Einrichten

Auch beim Client-Programm muss die ‘pfx’-Datei im Ausgabeverzeichnis landen.

Inkludieren der client.pfx Datei im Projekt

Inkludieren der client.pfx Datei im Projekt

Die Sektion ‘Service’ in appsettings.json wird erweitert mit dem Passwort und dem Datei-Namen.

{
  "Logging": {
  "LogLevel": {
    "Default": "Information",
    "Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "Service": {
    "CustomerId": 1,
    "DelayInterval": 3000,
    "ServiceUrl": "https://localhost:5001",
    "CertFileName": "client.pfx",
    "CertPassword": "P@ssw0rd" 
  }
}

Es ist nur möglich, Zertifikate zum HttpClientHandler zu zuweisen. Deshalb wird neu in worker.cs ein HttpClientHandler erstellt, das Zertifikat geladen, zugewiesen und mit dem Handler als Parameter der HttpClient erstellt.

protected DemoService.DemoServiceClient Client
{
    get
    {
        if (_client == null)
        {
            var cert = new X509Certificate2(
                _config["Service:CertFileName"], 
                _config["Service:CertPassword"]);
            var handler = new HttpClientHandler();
            handler.ClientCertificates.Add(cert);
            var client = new HttpClient(handler); 
            var opt = new GrpcChannelOptions()
            {
                HttpClient = client,
                LoggerFactory = _loggerFactory
            };
            ChannelBase channel = GrpcChannel.ForAddress(_config["Service:ServiceUrl"], opt);
            _client = new DemoService.DemoServiceClient(channel);
        }
        return _client;
    }
}

Wenn alle Schritte richtig durchlaufen sind, funktioniert die Client-Server Kommunikation wie vorher.  Die Ursachen von eventuellen Problemen sind leider nur sehr schwer zu finden. Im folgenden Beispiel wurde das Root-Zertifikat mit der Microsoft Management Console gelöscht. Beim Verbinden des Clients erscheint dann folgende Ausgabe.

Ausgabe vom gRPC Client wenn Zertifikat nich i.O. ist

Ausgabe vom gRPC Client wenn Zertifikat nicht i.O. ist

Die Fehlermeldung ‘The remote certificate is invalid according to the validation procedure’ kommt bei jedem Authentifizierungsproblem und ist nicht sehr hilfreich.

Fazit

Dieses Beispiel zeigt, dass es relativ wenig Schritte braucht um Client-Zertifikat Authentifizierung in den gRPC Server und Client einzubauen. Die Praxis zeigt aber auch, dass es sehr mühsam ist einen Fehler zu finden, falls die Absicherung der Kommunikation nicht auf Anhieb funktioniert.
Im nächsten Blog wird die Absicherung mit Access Tokens erklärt. Diese Technologie versichert den Server, dass der Client authentifiziert und autorisiert, ist um nur die erlaubte Funktionalität des APIs zu benutzen.

← Voriger Post
Nächster Post →

gRPC Tutorial Teil 3: Robustes Duplex-System

23. Dezember 2019
Erik Stroeken
3
duplex, gRPC, protobuf, streaming

Microsoft empfiehlt gRPC als Alternative für WCF. In .NET Core ist (bis jetzt) nur noch ein WCF-Client vorhanden. Ein wertvolles Feature von WCF ist die Duplex-Schnittstelle, über welche der Server seine Clients asynchron benachrichtigen kann.

Dieser Beitrag zeigt ein robustes gRPC-Client-Server System mit folgenden Merkmalen:

  • Einfacher Self-hosted Server: kein Kestrel, kein IIS.
  • Duplex-Verbindung: der Server muss den Client asynchron benachrichtigen können.
  • Das System soll Verbindungsunterbrüche vertragen können.
  • In der Praxis kann es sein, dass der Client schneller hochgefahren ist als der Server. Der Client muss damit umgehen können.
  • Das Beispiel beschreibt eine Peer-to-Peer Verbindung mit einen Server und einem Client.

Das Beispiel

Das Beispiel hat eine full-Duplex Peer-to-Peer Verbindung, also einen Client und einen Server mit asynchroner bidirektionaler Kommunikation. Der Client ruft beim Hochfahren ‘CheckServer()’ vom Server auf. Gibt es eine Exception, dann ist der Server nicht erreichbar und es wird in ‘RetryToConnectMs’ wieder versucht zu verbinden. Wenn der Aufruf erfolgreich war, wird ein Server-Stream geöffnet durch den Aufruf von ‘ServerNotification()’. Der Parameter Zykluszeit definiert, in welchem Abstand der Server den Client informieren soll (im Beispiel alle 5 Sekunden).

Wenn die Verbindung wegfällt, generiert der Server-Stream eine Exception. Der Client räumt den Server-Stream auf und versucht erneut zu verbinden wie beim Aufstarten.

Nach einem Verbindungsunterbruch ist der Server-Stream ungültig, weil es sein kann, dass beim Wiederverbinden der Client mit einem anderen Server verbunden wird (Load-Balancing). Das trifft natürlich bei dieser Peer-to-Peer Verbindung nicht zu.

Der Beispielcode kann hier heruntergeladen werden.

Die proto-Datei

Die Solution ist klassisch aufgeteilt auf je ein Server-, Client- und Contracts-Projekt. Die Protobuf-Datei befindet sich im DuplexWithServerStreamingContracts-Projekt und wird in C# Klassen konvertiert und in eine DLL kompiliert. Die meisten Beispiele im Internet inkludieren die Proto-Dateien vom Server-Projekt als Referenzen ins Client-Projekt. Das hat doppelte Kompilierung zur Folge und führt zu inkonsistenten ‘namespaces’ weil in der proto-Klasse mit ‘csharp_namespace’ der Namensraum der generierten C# Klassen festgelegt wird, diese jedoch beim Server oder Client unterschiedlich sind. Das separate Contracts-Projekt löst dieses Problem.

Die Protobuf-Datei hat folgenden Inhalt.

syntax = "proto3";
option csharp_namespace = "DuplexWithServerStreamingContracts.Protos";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

service DuplexService {
	rpc CheckServer (google.protobuf.Empty) returns (google.protobuf.Empty);
  rpc ServerNotification (NotificationIntervalMessage) returns (stream ServerNotificationMessage);
}

message NotificationIntervalMessage {
	int32 interval_ms = 1;
}

message ServerNotificationMessage {
	google.protobuf.Timestamp server_time = 1;
}

Damit die Build-Aktion der proto-Datei auf «Protobuf compiler» gesetzt werden kann, muss zuerst das NuGet-Paket ‘Grpc.AspNetCore’ referenziert werden. Zur Laufzeit wird das Paket aber nicht benötigt. Damit die DLLs nicht in der Ausgabe erscheinen, muss man ‘Exclude Assets’ vom NuGet-Paket auf ‘Runtime’ setzen.

Noser Engineering - gRPC - Exclude Assets

Exclude Assets = “Runtime”

Der Server

Das Beispiel benutzt nicht den IIS- oder Kestrel-Server, sondern den schlanken Server, welcher im Grpc.Core NuGet Paket integriert ist. Das Beispiel braucht nur die NuGet-Paketen ‘Grpc.Core’ und ‘Google.Protobuf’ und den Verweis auf DuplexWithServerStreamingContracts.

Noser Engineering - gRPC - Dependencies vom Server


Dependencies vom Server

Der DuplexService implementiert die zwei Methoden wie folgt:

class DuplexServiceRpc : DuplexService.DuplexServiceBase
{
    public override Task<Empty> CheckServer(Empty request, ServerCallContext context)
    {
        return Task.FromResult(new Empty());
    }

    public override async Task ServerNotification(NotificationIntervalMessage request, 
        IServerStreamWriter<ServerNotificationMessage> responseStream,
        ServerCallContext context)
    {
        DateTime nextNotificationTime = DateTime.UtcNow.AddMilliseconds(request.IntervalMs);
        CancellationToken cancellationToken = context.CancellationToken;
        do
        {
            if (DateTime.UtcNow > nextNotificationTime)
            {
                nextNotificationTime = DateTime.UtcNow.AddMilliseconds(request.IntervalMs);
                await responseStream.WriteAsync(
                    new ServerNotificationMessage
                    {
                        ServerTime = Timestamp.FromDateTime(DateTime.UtcNow)
                    }).ConfigureAwait(false);
            }
            await Task.Delay(10, cancellationToken).ConfigureAwait(false);
        } while (cancellationToken.IsCancellationRequested == false);
        cancellationToken.ThrowIfCancellationRequested();
    }
}

Die Methode CheckServer() macht nichts, muss aber trotzdem überladen werden, weil es sonst eine ‘NotImplementedException’ zur Folge hat.

ServerNotification() sendet jede IntervalMs Millisekunden eine ServerNotificationMessage mit der aktuellen Zeit durch den Server-Stream zum Client. Diese Methode simuliert das asynchrone Notifizieren vom Server zum Client. Diese Technik erlaubt es auch, einen Broadcast an alle Clients zu senden. Der Server muss sich dazu den IServerStreamWriterStreams von jedem Client merken und beim Broadcast die Clients einzeln aufrufen. Für ein Beispiel siehe den Post von Damien Bowden.

Weil es egal ist auf welchem Thread der Code nach einem ‘await’ ausgeführt wird, ist an jeden ‘await’ ein ConfigureAwait(false) angehängt. Das spart den Overhead vom Speichern und Zurücksetzen des Synchronization Contextes. Siehe Teil 11 der Serie C# Concurrency für Details.

Der Client

Der Client ist eine einfache Konsolen-Applikation und hat die gleichen Dependencies wie der Server: ‘Google.Protobuf’, ‘Grpc.Core’ und das ‘Contracts’-Projekt.

private const string Host = "localhost";
private const int Port = 8080;
private DuplexService.DuplexServiceClient _duplexService;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly CancellationToken _cancellationToken;
private Task _processServerNotificationTask;
public void Start()
{
    Channel channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
    _duplexService = new DuplexService.DuplexServiceClient(channel);
    // Task that sets up server stream to allow server to send events to client
    _processServerNotificationTask = Task.Factory.StartNew(ProcessServerNotification, _cancellationToken, 
                                       TaskCreationOptions.LongRunning, TaskScheduler.Default);}

Die Methode erstellt den gRPC Client und startet einen Endlos-Thread, welcher die Verbindung kontrolliert und die Benachrichtigungen vom Server verarbeitet. Die Option LongRunning gibt dem Task Scheduler den Hinweis, dass der Thread lange läuft. Die heutige Implementation des Task Schedulers erzeugt in diesem Fall einen dedizierten Thread, statt einen Thread aus dem Threadpool zu verwenden. Siehe Blog C# Concurrency Teil 8.

Heruntergefahren wird der Client wie folgt:

public void Stop()
{
    _cancellationTokenSource.Cancel();
    try
    {
        _processServerNotificationTask.Wait(1000);
    }
    // OperationCanceledException is expected and will be catched and filtered out here...
    catch (AggregateException ex)
    {
        List<Exception> exceptions =
            ex.Flatten().InnerExceptions.Where(e => !(e is OperationCanceledException)).ToList();
        foreach (Exception e in exceptions)
        {
            Console.WriteLine(e);
        }
    }
    _cancellationTokenSource?.Dispose();
}

Der Task wird mit dem CancellationToken gestoppt. Wait() blockiert, bis der Task beendet ist. Die erwartete OperationCanceledException wird ignoriert und der TokenSource disposed.

Kommen wir nun zum Herz der Applikation: ProcessServerNotification().

private const int RetryToConnectMs = 10000;
...
private void ProcessServerNotification()
{
    AsyncServerStreamingCall<ServerNotificationMessage> serverStreamingCall = null;
    NotificationIntervalMessage msg = new NotificationIntervalMessage
    {
        IntervalMs = 5000  // Tell the server to send its time every 5 seconds
    };
    do
    {
        if (serverStreamingCall == null)
        {
            try
            {
                _duplexService.CheckServer(new Empty());
                serverStreamingCall = _duplexService.ServerNotification(
                    msg, null, null, _cancellationToken);
                Console.WriteLine("Server is online.");
                continue;
            }
            catch 
            {
                Console.WriteLine("Failed to connect to server...");
            }
            Task.Delay(RetryToConnectMs, _cancellationToken).Wait(_cancellationToken);
        }
        else
        {
            try
            {
                // Did the server send a response (MoveNext() returns true)?
                Task<bool> moveNextTask = serverStreamingCall.ResponseStream.MoveNext();
                moveNextTask.Wait(_cancellationToken);
                if (moveNextTask.Result)
                {
                    DateTime serverDateTime =
                        serverStreamingCall.ResponseStream.Current.ServerTime.ToDateTime();
                    Console.WriteLine(
                        $"Received server notification message. Server time is {serverDateTime}.");
                }
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch 
            {
                serverStreamingCall.Dispose();
                serverStreamingCall = null;
                Console.WriteLine("Server is offline.");
            }
        }
        // Put task to cancelled if cancellation token is set
        _cancellationToken.ThrowIfCancellationRequested();
    } while (true);
}

Beim Aufstarten ist ‘serverStreamingCall’ ‘null’ und ‘CheckServer()’  wird aufgerufen, um zu prüfen, ob der Server online ist. Die Methode wirft eine Exception, wenn der Server nicht erreichbar ist. Es wird dann ‘RetryToConnectMs’ Millisekunden gewartet, bevor wieder versucht wird, den Server zu erreichen. Wenn die Verbindung in Ordnung ist, wird ‘ServerNotification()’ einmal aufgerufen, um den Server-Stream aufzusetzen.

Leider wirft ‘ServerNotification()’ keine Exception wenn der Server nicht erreichbar ist, sonst könnte man komplett auf ‘CheckServer()’ verzichten.

ResponseStream.MoveNext() vom zurückgegebenen Stream liefert ‘Task<bool>’ zurück. Mit ‘Wait(_cancellationToken)’ wird blockierend auf das Resultat gewartet, welches sich danach in der Eigenschaft Current befindet. Wenn die Verbindung zum Server unterbrochen wird gibt es eine RpcException ‘stream removed’ worauf der Server-Stream aufgeräumt wird und das Ganze von vorne anfängt.

Es wird im Client bewusst auf async\await verzichtet, weil das Ganze auf einem dedizierten Thread läuft. Mit async\await würde einen extra Thread aus dem Threadpool verschwendet werden. Siehe Teil 11 der Serie C# Concurrency für Details.

Fazit

Das Beispiel zeigt wie man mit 2 Methoden ein robustes Server-Client-System mit Duplex-Schnittstelle erstellen kann. Es wird der einfache Server von gRpc.Core benutzt und nur die Designtime Features von ASP.NET Core: die ASP.NET Core DLLs werden nicht ins Ausgabe-Verzeichnis kopiert.

Im nächsten Teil wird die Kommunikation zwischen Client und Server abgesichert mit selbst-signierten Client-Zertifikaten. Diese Stufe der Absicherung ist zureichend, wenn nicht von aussen auf die Systeme zugegriffen wird.

← Voriger Post
Nächster Post →
‹ Previous123456Next ›Last »

Tag Cloud

.NET android Angular AngularJs Arduino ASP.Net automated testing Azure Big Data C# C++ Cloud continuous integration Elm Embedded Führung gRPC Internet of Things IoT Java Javascript M2M OWASP Projektmanagement protobuf Python Raspberry Pi Reactive Programming REST Scrum Security Softwarequalität SPA Testen testing Testmanagement Teststrategie UX Visual Studio WebAPI windows WPF Xamarin Xamarin.Android Xamarin.Forms

Archive

Current Posts

  • Akzente setzen mit der Android Splash Screen API unter .NET MAUI
  • Do You have Your Personal Space?
  • Automated provisioning with ARM Templates
  • Asynchrone Beobachtungen und Versprechungen in Angular
  • Simplify Your Automated Tests With Fluent Syntax

Last Comments

  • Hans Reinsch bei Der Safety-Plan: Die wichtigsten Antworten mit Checkliste
  • George H. Barbehenn bei Modeling Optocouplers with Spice
  • Noser Blog Touch-Actions in Xamarin.Forms - Noser Blog bei Mach mehr aus Animationen in Xamarin.Forms mit SkiaSharp
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei First run of the Trusted Firmware (TFM) application
  • Noser Blog First run of the Trusted Firmware (TFM) application - Noser Blog bei Focus on the Secure Storage service of Trusted Firmware (TFM)

Popular Posts

Xamarin.Android Code Obfuscation

6 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch

5 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick

4 Comments

Contact us

  1. Name *
    * Please enter your name
  2. Email *
    * Please enter a valid email address
  3. Message *
    * Please enter message
© 2013 NOSER ENGINEERING AG. All rights reserved. Datenschutz | Cookie-Richtlinie