Dieser Artikel bildet den Abschluss unseres Chat-Clients. Nachdem wir ein Konzept erarbeitet haben und der Server entwickelt wurde, brauchen wir nun einen Client. Dieser stellt eine Verbindung mit dem Server her. Anschließend kann mit allen angemeldeten Personen gechattet werden.

Aufgaben des Clients

Da der Server den größten Teil der Funktionslogik besitzt (und die Datenhaltung organisiert) muss der Client im Grunde nur den Nachrichtenaustausch bewältigen.

Dies bedeutet, wir benötigen einen Thread zum Einlesen der Eingaben und einen Thread für die Ausgaben. Die beiden Threads dienen wieder dazu, dass parallel gesendet und empfangen werden kann. Es ist ähnlich wie beim Server. Wenn die Anwendung beispielsweise auf Antworten vom Server wartet, ist der Hauptthread blockiert. Es können keine Benutzereingaben verarbeitet werden.

Dazu starten wir einen Thread, der nur die Nachrichten einliest:

// ClientHandler.cs
public void Start() {
  Thread receiveThread = new Thread(ReceiveMessages);
  receiveThread.Start();

  // .. Schleife zum Lesen der Eingaben
}

private async void ReceiveMessages() {
  // Solange die Nachrichten lesen, wie der Client verbunden ist
  while (_isConnected) {
    //Asynchrones warten auf eine Eingabe ohne den Thread zu blockieren
    string result = await _reader.ReadLineAsync();
    Console.WriteLine(result);

    // Der Server schreibt eine Logoff-Nachricht zurück. Damit weiß der Thread, dass
    // er beendet werden soll. Dient als Bestätigung.
    if (result.StartsWith("Logoff")) {
      _isConnected = false;
      Console.WriteLine("Please press any key to quit!");
    }
  }
}

Das Lesen erfolgt in einer Schleife. Die Boolean-Variable _isConnected gibt an, ob der Client mit dem Server verbunden ist. Wird die Variable auf false gesetzt, bricht der Client den Thread ab. Da die Schleife damit zu Ende ist, wird ebenfalls der Thread beendet.

Das Schreiben erfolgt ebenfalls in einer Schleife:

// ClientHandler.cs
// Benutzereingaben in einer Schleife pollen
while (_isConnected) {
  // Der Aufruf ReadLine() blockiert die weitere Ausführung.
  string input = Console.ReadLine();

  // Nachdem der String von der Konsole eingelesen wurde, wird der String in den
  // Stream geschrieben und abgesendet.
  _writer.WriteLine(input);
  _writer.Flush();
}

Zu beachten ist hierbei der Aufruf von Stream.Flush(). Ohne diesen Befehl wird die Zeichenfolge solange im Puffer gespeichert, bis das Limit erreicht wurde. Mit Flush() wird der Puffer geleert und die Nachricht gesendet.

Zusammenfassung und Ausblick

Wir haben uns zusammen angesehen, wie ein Chatserver aussehen kann. Hierzu haben wir zu Beginn ein kurzes Konzept entwickelt und uns angesehen, welche Technologien wir verwenden könnten. Das Protokoll und die Befehle sind rudimentär gehalten, um eine Basisfunktionalität bereitzustellen.

Eine Fehlerbehandlung, wie falsche Serveradresse oder eine unterbrochene Netzwerkverbindung, gibt es praktisch nicht. Ich wollte hiermit ein kurzes Beispiel vorstellen, wie anhand von ein paar Angaben eine Lösung entwickelt werden kann. Für einen produktiven und stabilen Einsatz muss dieser Punkt auf jeden Fall ausgebaut werden.

Da das Projekt öffentlich auf GitHub gepostet ist, darf gerne jeder von euch seine Verbesserungen einfließen lassen oder auf der Basis ein eigenes Projekt entwickeln 🙂

Mögliche Probleme und Fehler

Wenn ein Client sich einfach abmeldet, wird im Server eine Exception ausgelöst. Durch ein try-catch wird der Fehler abgefangen, ausgegeben und der Client aus dem Speicher entfernt. Der Thread wird ebenfalls gestoppt.

Ein anderes Problem stellt die Skalierbarkeit dar. Momentan wird alles über Threads realisiert. Wenn sich zu viele Clients anmelden kann es sein, dass der Server irgendwann überlastet ist. Einen Leistungstest, wie viele Clients maximal möglich sind, habe ich nicht durchgeführt.

Wenn der Client beim Einloggen einen falschen Endpunkt angibt, beispielsweise durch einen Tippfehler, gibt es eine Exception und der Client wird direkt beendet.

Mögliche Weiterentwicklungen

  • Grafische Benutzeroberfläche für Clients
  • Chaträume bzw. Channels
  • Login mit Benutzername und Kennwort (Kontoverwaltung)
  • Bessere Fehlerbehandlung
  • Benachrichtigungen vom Server (Wartungsarbeiten, neuer Benutzer hat sich angemeldet)
  • Neue Funktionen wie /getTime, um die aktuelle Serverzeit zu erhalten
  • Dateiaustausch
  • Verschlüsselung von Nachrichten
  • Und vieles mehr…

Quellcode

Das Projekt findet ihr unter Github: https://github.com/mbedded/BlogContent/tree/master/2017-05-03-ChatPrototype

Viel Spaß beim ausprobieren 🙂 Für Anmerkungen könnt ihr gerne die Kommentarfunktion benutzen oder ein Ticket im Repository eröffnen.

Edit 2018-09-07:
Link für das GitHub Repository angepasst.