Bidirektionale Kommunikation mit MQTT in .NET MAUI
Als Mobile-App-Entwickler:innen müssen wir ständig Informationen zwischen App und Backend austauschen. In den meisten Fällen ist eine RESTful-API die Lösung. Aber was, wenn ein konstanter Datenfluss in beide Richtungen benötigt wird? In diesem Beitrag schauen wir uns MQTT an und wie man eine einfache Chat-App in .NET MAUI erstellt.
BlogBidirektionale Kommunikation mit MQTT in .NET MAUI
MQTT steht für Message Queue Telemetry Transport und ist ein Machine-to-Machine-(M2M)-Protokoll. Es dient dem Austausch von Nachrichten zwischen Sensoren, Smartphones, Autos oder allem, was dir sonst noch einfällt. Dafür braucht man einen Subscriber und einen Publisher, die beide mit einem Server – dem sogenannten Broker – verbunden sind.
Wie funktioniert MQTT?
MQTT nutzt ein Publish/Subscribe-Modell, um Nachrichten an einen oder mehrere Clients zu senden. Clients haben keine Adressen wie in E-Mail-Systemen, und Nachrichten werden ihnen nicht direkt zugestellt. Stattdessen veröffentlicht ein Publisher Nachrichten auf einem Topic, und ein Subscriber muss dieses Topic abonnieren, um die Nachricht zu erhalten – wie beim TV- oder Radio-Prinzip.
Die Aufgabe eines MQTT-Brokers ist es, Nachrichten anhand ihrer Topics zu filtern und sie anschließend an die entsprechenden Subscriber zu verteilen. Ein Client kann diese Nachrichten empfangen, indem er auf demselben Broker das Topic abonniert. Es gibt keine direkte Verbindung zwischen Publisher und Subscriber. Alle Clients können veröffentlichen („broadcasten“) und abonnieren („empfangen“).
Was ist ein MQTT-Broker?
Der Broker ist dafür verantwortlich, alle Nachrichten zu empfangen, zu filtern, zu bestimmen, wer welches Topic abonniert hat, und die Nachricht an diese Clients zu senden. Der Broker ist das Herzstück jedes Publish/Subscribe-Protokolls. Je nach Implementierung kann ein Broker Millionen gleichzeitig verbundener MQTT-Clients verwalten.
Was ist ein MQTT-Client?
Ein MQTT-Client ist jedes Gerät (vom Microcontroller bis zum vollwertigen Server), das eine MQTT-Bibliothek ausführt und sich über ein Netzwerk mit einem MQTT-Broker verbindet.
Clientname bzw. Client-ID
Alle Clients müssen einen Clientnamen oder eine Client-ID haben, um am Nachrichtenaustausch teilzunehmen. Der Broker nutzt diese, um Subscriptions zu verfolgen; sie muss daher eindeutig sein. Versuchst du, dich mit demselben Namen wie ein bereits verbundener Client zu verbinden, wird dessen Verbindung getrennt. Da die meisten Implementierungen nach einer Trennung automatisch reconnecten, kann das zu einer Endlosschleife aus Disconnect und Connect führen.
Clean Sessions
Standardmäßig stellen MQTT-Clients eine Clean Session mit einem Broker her. Das bedeutet, der Broker soll sich nichts über den Client merken, wenn er sich trennt. In einer unclean session merkt sich der Broker Client-Subscriptions und kann unzugestellte Nachrichten für den Client speichern. Das hängt allerdings von der Quality of Service (QoS) beim Abonnieren/Publishen der Topics ab.
Einen einfachen MQTT-Server mit MQTTnet erstellen
Für unsere .NET-MAUI-Chat-App benötigen wir einen Broker. Dazu erstellen wir ein neues Console-Projekt und installieren die MQTTnet-Pakete. Mit folgendem Code erzeugen wir den einfachsten MQTT-Server mit einem TCP-Endpoint (Standard: Port 1883) und stoppen ihn per Tastendruck:
var mqttServer = new MqttFactory().CreateMqttServer();
mqttServer.StartAsync(new MqttServerOptions());
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
mqttServer.StopAsync();
Den MQTT-Server mit MqttServerOptionsBuilder konfigurieren
Mit dem MqttServerOptionsBuilder kannst du den Broker anpassen, z. B. den Port auf 1884 ändern oder eine Server-Client-ID setzen. Außerdem kannst du alle empfangenen Nachrichten in einer Logdatei speichern – etwa in MessagesLog.txt.
const string LogFilename = "/Users/{user}/Desktop/MessagesLog.txt";
var option = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.WithDefaultEndpointPort(1884);
var mqttServer = new MqttFactory().CreateMqttServer(option.Build());
mqttServer.InterceptingPublishAsync += context =>
{
var message = Encoding.UTF8.GetString(context.ApplicationMessage.Payload);
if (File.Exists(LogFilename) == false)
File.CreateText(LogFilename);
File.AppendAllText(LogFilename,
$"Client: {context.ClientId}, sent time: {DateTime.Now}, message: {JsonConvert.SerializeObject(message)} \r\n");
return CompletedTask.Instance;
};
Nachrichten für inaktive Clients aufbewahren
Ist ein Client getrennt, kann der Broker empfangene Nachrichten speichern und sie beim erneuten Verbinden versenden. Diese Nachrichten nennt man Retained Messages. Mehr zum Retained-Flag gibt’s in der MQTT Essentials*-Reihe von HiveMQ. MQTTnet bietet Events zum Speichern und Laden solcher Nachrichten. Füge die RetainedMessage-Events zu deinem mqttServer hinzu. Das Senden übernimmt der Server automatisch. Den WithApplicationMessageInterceptor kannst du weglassen, wenn du ihn nicht brauchst.
Falls du deine Logs nicht findest, suche die Datei mit dem Finder (macOS) oder dem Windows Explorer – sie sollte vorhanden sein.
Das ist alles, was wir für den Broker brauchen.
Du hast ein Projekt in Xamarin oder .NET MAUI? Wir helfen dir, Zeit zu sparen.
Bevor du alleine weitermachst, wirf einen Blick auf unsere Services. Unser erfahrenes Team bespricht dein Projekt gern mit dir und hilft dir dabei, es auf exzellentem Niveau fertigzustellen.
Mit dem Broker am Start erstellen wir nun eine .NET-MAUI-App, die MQTTnet nutzt, um Nachrichten zwischen Benutzer:innen nahezu in Echtzeit auszutauschen.
Zuerst legen wir ein neues Projekt an und richten den MQTT-Client in einer ViewModel-Klasse ein. Für WithTcpServer verwendest du deine IP-Adresse, und als Client-ID den aktuellen User.
IMqttClient _mqttClient;
string _currentUser = "user1";
string _chatPartner = "user2";
public async Task CreateClient()
{
// Clientobjekt erzeugen
_mqttClient = new MqttFactory().CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithClientId(_currentUser)
.WithTcpServer("YOUR IP", 1884)
.Build();
}
Wir hängen außerdem Events in CreateClient an, um benachrichtigt zu werden, wenn der Client verbunden/getrennt ist oder Nachrichten empfängt.
Zuletzt verbinden wir uns mit dem Broker und abonnieren unseren Chat-Kanal, um über neue Nachrichten informiert zu werden.
// Mit dem Broker verbinden
await _mqttClient.ConnectAsync(options);
await _mqttClient.SubscribeAsync(
new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter($"chatChannel/{_currentUser}")
.Build());
Jetzt bereiten wir die Seite vor, um empfangene Nachrichten anzuzeigen und eigene Nachrichten zu senden. Zum Senden erstellen wir eine MqttApplicationMessage, die den Empfänger-Kanal und den Text enthält. Mit PublishAsync wird sie verschickt.
Tippe einfach deine Nachricht ein und sende sie. Beide Simulatoren sollten die gesendete Nachricht sehen. Du kannst auch beliebig viele Simulatoren starten und dasselbe Topic abonnieren. Wenn ein Client eine Nachricht an dieses Topic veröffentlicht, erhalten alle Subscriber die Nachricht. Denk daran, für jeden Client eine eigene Client-ID zu verwenden.
Gefällt dir unser Ansatz?
Du hast es bis hierher geschafft – als Entwickler:in hast du einen Einblick in unsere Arbeit erhalten. Migrationen sind nur ein Teil unserer Xamarin- und .NET-MAUI-Leistungen. Wir unterstützen dich in allen Bereichen der App-Entwicklung.
Wenn du die App in den Hintergrund schickst, erhältst du weiterhin Nachrichten, solange deine App lebt – du wirst also nicht vom Server getrennt. Mit Local Notifications kannst du Nutzer:innen über neue Nachrichten informieren.
Aber was passiert, wenn deine App vom OS beendet wurde?
In diesem Fall weißt du nicht, ob Nachrichten eingegangen sind, da du getrennt bist. Das lässt sich mit Push-Benachrichtigungen lösen: Sie hängen nicht von deiner Broker-Verbindung ab. Sobald der/die Nutzer:in die Benachrichtigung sieht, kann er/sie die App starten, erneut verbinden und die neuesten Nachrichten abrufen.
Lass mich wissen, ob dir dieser Artikel geholfen hat. Bei Fragen melde dich gern.
Martin Luong
Mit über 7 Jahren Erfahrung in der Entwicklung von Cross-Plattform-Apps mit Xamarin und .NET MAUI zählt Martin zu den alten Hasen bei Cayas Software. Kunden aus Agrar-, Logistik- oder der Gesundheitsbranche schätzen seine ruhige Art und das analytische Vorgehen bei der Umsetzung ihrer Xamarin und .NET MAUI-Projekte. Ein Teil aus seiner täglichen Arbeit mit Xamarin und .NET MAUI teilt er in seinen Artikeln.
Ich arbeite derzeit an der Portierung einer Xamarin Forms App zu .NET MAUI. Die App verwendet auch Karten von Apple oder Google Maps, um Standorte anzuzeigen. Obwohl es bis zur Veröffentlichung von .NET 7 keine offizielle Unterstützung in MAUI gab, möchte ich Ihnen eine Möglichkeit zeigen, Karten über einen benutzerdefinierten Handler anzuzeigen.
.NET MAUI ermöglicht es uns, plattform- und geräteunabhängige Anwendungen zu schreiben, was eine dynamische Anpassung an die Bildschirmgröße und -form des Benutzers erforderlich macht. In diesem Blog-Beitrag erfahren Sie, wie Sie Ihre XAML-Layouts an unterschiedliche Geräteausrichtungen anpassen können. Dabei verwenden Sie eine ähnliche Syntax wie OnIdiom und OnPlatform, die Ihnen vielleicht schon bekannt ist.
This post is a continuation of the Hackathon topic post, where the technical implementation of voice commands in .NET MAUI is revealed, as well as the challenges the development team faced and how they successfully solved them.