12. Feb 2024
Lesedauer 9 Min.
Message Queuing Telemetry Transport mit C#
Machine-to-Machine-Kommunikation mit MQTTnet
Ein MQTT-Client-App-Beispiel mit Visual Studio und C#.

Message Queuing Telemetry Transport, kurz MQTT, stellt ein offenes Nachrichtenprotokoll für die Machine-to-Machine-Kommunikation (M2M) dar. MQTT wurde offiziell ab 2010 veröffentlicht und hat sich in den letzten Jahren zum De-facto-Standard für das Internet of Things (IoT) entwickelt.Hinter dem MQTT-Protokoll verbirgt sich eine leichtgewichtige Publish-Subscribe-Lösung, bei der Anwender Topics (Themen) einrichten können, über die Clients (als Publisher/Verleger) Nachrichten bereitstellen und andere Clients (als Subscriber/Abonnenten) Nachrichten entnehmen. Clients können unter MQTT beide Rollen besitzen und damit als Publisher wie auch als Subscriber fungieren. Um Art und Inhalt der Nachricht kümmert sich MQTT nicht; das bleibt der jeweiligen Implementierung und Vorgehensweise überlassen. Bild 1 zeigt die Architektur von MQTT.

Architektur von MQTT (Bild 1)
Autor
Der MQTT-Broker stellt in diesem Technologie-Stack die Vermittlereinheit dar, die den MQTT-Clients die Kommunikation ermöglicht. Insbesondere erhält ein MQTT-Broker Nachrichten, die vom Publisher veröffentlicht werden, filtert die Nachrichten nach Topics und verteilt diese an die Subscriber.
MQTT-Clients
Der Client ist der Endnutzer der Kommunikation und auch derjenige, der Nachrichten aktiv sendet. Ein Client kann zu einem Zeitpunkt sowohl Nachrichten eines Topics empfangen (Subscriber) als auch Nachrichten in dem gleichen Topic veröffentlichen (Publisher). Jeder Client identifiziert sich durch eine eindeutige Client-ID, die auch seine Session vollständig identifiziert, denn MQTT ist für die Client-Seite komplett zustandslos (stateless).Publisher senden Nachrichten auf die bereits genannten Topics (Themen). Die Topics sind hierbei hierarchisch strukturiert und ähneln von der Schreibsyntax her der Ordnerstruktur in einem Filesystem. Auch bei Topics ist der Hierachielevel jeweils mit einem Slash (/) gekennzeichnet.Topics müssen von den Clients abonniert werden, um Nachrichten zu empfangen. Schließt sich also ein neuer Client dem Nachrichtennetz an und schickt er dem MQTT-Broker eine Subscription mit dem gewünschten Topic, so wird der MQTT-Broker alle Nachrichten mit dem Topic an den Subscriber weiterleiten.Durch die Verwendung von Single-Level-Wildcards (+) und Multi-Level-Wildcards (#) können Subscriber mehrere Topics und deren Sub-Topics gleichzeitig abonnieren.MQTT Quality of Service (QoS)
MQTT baut auf dem Transmission Control Protocol (TCP) als Transportprotokoll auf. Es erlaubt die Verwendung dreier unterschiedlicher Servicequalitäten (Quality of Service, QoS) mit den Angaben 0, 1 und 2:- Bei der niedrigsten Stufe 0 handelt es sich im Prinzip um ein Fire-and-forget-Paket. Es gibt also keine Garantie, dass die Nachricht überhaupt ankommt.
- Bei Level 1 ist sichergestellt, dass die Nachricht mindestens einmal in der Topic Queue landet. Hier wartet der Sender auf eine Bestätigung des Empfängers (Pushback) und ist auch verpflichtet, die Nachricht erneut zu verschicken, sofern keine Empfangsbestätigung eintrifft.
- Beim höchsten QoS-Level 2 garantiert der MQTT-Broker, dass die Nachricht genau einmal abgelegt wird. Um diese Garantie einhalten zu können, verwendet MQTT eine zweistufige Empfangsbestätigung.
Entkopplung
Somit kommuniziert der MQTT-Broker Nachrichten sowohl mit dem Publisher wie auch mit den Subscribern. Diese Art der Architektur ermöglicht eine vollständige Entkopplung der Kommunikationsteilnehmer. Es handelt sich hier also um eine 1:N-Kommunikation. Clients bleiben ständig mittels einer bestehenden TCP-Verbindung mit dem MQTT-Broker verbunden, wobei eine Nachricht im Push-Verfahren ohne Verzögerung an die registrierten Clients gesendet wird. Sowohl Subscriber als auch Publisher müssen nur im Kontext mit dem MQTT-Broker stehen und brauchen sich gegenseitig nicht zu kennen.Folglich stellt der MQTT-Broker die Vermittlungsplattform des MQTT-Nachrichtenaustauschs dar. Bei einem Ausfall des MQTT-Brokers bricht die ganze Kommunikation zusammen, da die Clients nicht mehr untereinander kommunizieren können. Hierfür gibt es die Möglichkeit, verschiedene MQTT-Broker miteinander zu verbinden, um ein redundantes System aufzubauen, sofern dies erforderlich ist.Aber neben dem reinen Austausch von Nachrichten besitzt MQTT eine Fülle von weiteren Features, die es als Kommunikationsprotokoll für das Internet of Things optimal machen, so zum Beispiel das aufgezeigte Konzept von Quality of Service. Des Weiteren unterstützt MQTT sogenannte Retained Messages, Nachrichten bei Verbindungsabbruch (Last Will and Testament, kurz LWT) und Persistent Sessions.Über das Feature Retained Messages kann ein Publisher beim Versenden einer Nachricht diese als retained (beibehalten) markieren. Der MQTT-Broker speichert diese Nachricht nun für das Topic ab, damit alle neuen Subscriber auf diesem Topic direkt diese Nachricht zugestellt bekommen.Durch die bestehende TCP-Verbindung der Clients zum MQTT-Broker wird, wie schon angesprochen, eine ereignisgetriebene Kommunikation umgesetzt. Ein Vorteil dieser Verbindung ist, dass der MQTT-Broker bei einem Verbindungsabbruch eines Clients diesen Abbruch sofort erkennen kann.Das Feature Last Will and Testament von MQTT erlaubt einem Client, eine Nachricht am MQTT-Broker zu hinterlegen, die vom Broker versendet wird, sobald die TCP-Verbindung geschlossen wurde. Des Weiteren kann ein Client beim Verbindungsaufbau entscheiden, ob er eine persistente Session anlegen möchte. Diese Session bleibt auch nach Beendigung einer TCP-Verbindung bestehen und wird bei einer erneuten Verbindung fortgeführt.MQTT-Broker
Inzwischen gibt es eine Vielzahl von fertigen MQTT-Broker-Implementierungen. Empfehlenswert im Bereich Open Source sind der Message Broker (Server) von MQTTnet [1] oder auch Mosquitto aus der Eclipse Foundation [2], die beide das MQTT-Protokoll implementieren.Im kommerziellen Bereich finden Sie vor allem bei HiveMQ [3] einen Broker beziehungsweise auch eine Cloud-Umgebung (HiveMQ Cloud), die ganz ohne lokale Installation auskommt und sofort zum Testen genutzt werden kann.Ein weiteres wichtiges Grundprinzip von MQTT ist die Einfachheit der Umsetzung am Client.Schnell und einfach mit C# einsteigen
Nachdem Sie nun ein wenig über MQTT erfahren haben, können Sie ohne weitere Umwege in die Implementierung eines MQTT-Clients mit C# einsteigen.In diesem Artikel wird die Open-Source-MQTT-Client-Bibliothek MQTTnet verwendet, die in ein Visual-Studio-Projekt eingebettet wird. Daher sollten Sie die Entwicklungsumgebung Visual Studio in einer aktuellen Version (ab Community Edition) benutzen. Des Weiteren sind Grundkenntnisse in der C#-Programmierung mit Visual Studio von Vorteil.Erste Schritte
Die Entwicklung einer IoT-Applikation mit C# unterscheidet sich nüchtern betrachtet nicht von einer bekannten klassischen Applikation in C#. Sie brauchen deshalb glücklicherweise das Rad nicht neu zu erfinden. Inzwischen können Sie auch bei IoT-Produkten im .NET-Umfeld auf eine Reihe von quelloffenen oder kommerziellen Frameworks zugreifen. Für das Entwickeln eines MQTT-Clients mit C# benötigen Sie nur folgende Tools:- einen MQTT-Broker, zum Beispiel MQTTnet Server
(Broker), HiveMQ oder Mosquitto, - eine MQTT-Bibliothek oder ein MQTT-Framework,
- Visual Studio in einer aktuellen Version.
- asynchrone Unterstützung,
- TLS (Transport Layer Security)-Unterstützung für Clients und Server,
- erweiterte Kommunikationskanäle wie In-Memory, TCP, TCP & TLS sowie WebSocket,
- nur Low-Level-Implementierung von MQTT und somit keinen Overhead,
- Performance-optimiert,
- einheitliches API für alle unterstützten Versionen des MQTT-Protokolls.
Das Visual-Studio-Projekt
Das nachfolgende Visual-Studio-Projekt soll Ihnen veranschaulichen, wie Sie einen MQTT-Client für Ihre eigenen Zwecke erstellen. Im ersten Schritt soll hier aber nur eine Nachricht an den MQTT-Broker gesendet werden und es ermöglicht werden, eine gewünschte Nachricht zu empfangen. Der Client soll also in dem Beispiel sowohl als Publisher wie auch als Subscriber fungieren.Daher ist ein GUI-Layout nicht unbedingt vonnöten. Sie können dieses aber ganz frei und individuell für Ihre Applikation entwickeln; nutzen Sie hierfür der Einfachheit halber eine Windows-Forms-, eine WPF- (.NET Framework) oder auch eine ASP.NET-Core-Web-App.Legen Sie für das Beispiel ein neues Console App-Projekt in Visual Studio an. Vergeben Sie als Project name den Begriff MQTTDemo und wählen Sie .NET 7.0 als Framework aus.Nach dem Klick auf die Schaltfläche Create erstellt Visual Studio automatisch ein lauffähiges Konsolenprogramm mit der Ausgabe Hello, World!MQTTnet integrieren
Als Erstes können Sie für das Beispiel die beiden Codezeilen in der Klasse Program.cs entfernen. Der MQTT-Stack wird über den NuGet-Paketmanager bereitgestellt. In Ihrem Visual-Studio-Projekt verwenden Sie einfach den Menüaufruf Tools | NuGet Package Manager | Manage NuGet Packages for Solution …, tragen dort in das Suchfeld MQTTnet ein und klicken auf Browse.Wählen Sie das NuGet-Paket MQTTnet in der Auswahlliste, aktivieren Sie das Projekt auf der rechten Seite und klicken Sie auf Install(Bild 2). Ist die Installation abgeschlossen, so ist das Visual-Studio-Projekt für die Implementierung eines MQTT-Clients vorbereitet. Dank der Bibliothek MQTTnet kann der Client mit wenigen Schritten aufgebaut werden.
Auswahl des NuGet-Pakets MQTTnet (Bild 2)
Autor
MQTT-Client erstellen
Ist das NuGet-Paket installiert, fügen Sie der Klasse Program.cs die benötigte Using-Deklaration für den MQTTnet-Client hinzu. Als Nächstes wird mithilfe der Klasse MqttClientOptionsBuilder eine Instanz für den Zugriff auf den Broker erstellt. Hierbei werden die erforderlichen Optionen wie Broker-Adresse, Port, Benutzername und Passwort festgelegt. Um eine Verbindung mit dem MQTT-Broker aufzubauen beziehungsweise zu erstellen, genügt der Codeausschnitt aus Listing 1.Listing 1: MQTT-Client erstellen
<span class="hljs-keyword">using</span> MQTTnet;<br/><span class="hljs-keyword">using</span> MQTTnet.Client;<br/><span class="hljs-keyword">using</span> MQTTnet.Protocol;<br/><span class="hljs-keyword">string</span> broker = <span class="hljs-string">"myBroker.com"</span>;<br/><span class="hljs-keyword">int</span> port = <span class="hljs-number">8883</span>;<br/><span class="hljs-keyword">string</span> clientId = Guid.NewGuid().ToString();<br/><span class="hljs-keyword">string</span> topic = <span class="hljs-string">"Csharp/mqtt"</span>;<br/><span class="hljs-keyword">string</span> username = <span class="hljs-string">"myName"</span>;<br/><span class="hljs-keyword">string</span> password = <span class="hljs-string">"myPassword"</span>;<br/><span class="hljs-keyword">var</span> factory = <span class="hljs-keyword">new</span> MqttFactory();<br/><span class="hljs-keyword">var</span> mqttClient = factory.CreateMqttClient();<br/><span class="hljs-keyword">var</span> options = <span class="hljs-keyword">new</span> MqttClientOptionsBuilder()<br/> .WithTcpServer(broker, port)<br/> .WithCredentials(username, password)<br/> .WithClientId(clientId)<br/> .WithCleanSession()<br/> .Build();
Mit den Variablen werden die benötigten Werte für den Verbindungsaufbau, das Thema (Topic) und die Client-ID festgelegt. Da jeder MQTT-Client eine eindeutige Client-ID benötigt, wird über die Methode Guid.NewGuid eine neue eindeutige Kennung generiert.Über die Methode MqttFactory wird eine neue Client-Factory erstellt und aus dieser über die Methode CreateMqttClient eine Client-Instanz angelegt.Zum Schluss werden über die Methode MqttClientOptionsBuilder die Optionen für den Client festgelegt. Möchten Sie für die Kommunikation mit dem Broker das Protokoll TSL/SSL (Transport Layer Security / Secure Sockets Layer) verwenden, so erweitern Sie einfach die Optionen mit der Methode WithTls wie folgt:
.WithTls(
o =>
{
o.SslProtocol = SslProtocols.Tls12;
<span class="hljs-keyword">var</span> certificate = <span class="hljs-keyword">new</span> X509Certificate(
<span class="hljs-string">"/.../myCertificate.crt"</span>, <span class="hljs-string">""</span>);
o.Certificates = <span class="hljs-keyword">new</span> <span class="hljs-built_in">List</span><X509Certificate> {
certificate };
}
Über SslProtocols.Tls12 wird der Standardwert manuell mit der angegebenen Version festgelegt. Geben Sie diesen Wert nicht an, wird er durch das Betriebssystem bestimmt. Danach werden die aktuellen Zertifikatsdateien mit ihrem Dateipfad angegeben.
Verbindung herstellen
Über den Aufruf mqttClient.ConnectAsync stellen Sie eine asynchrone Verbindung zum Broker her. Der Methodenaufruf im Code erfolgt einfach über
<span class="hljs-keyword">var</span> connectResult =
<span class="hljs-keyword">await</span> mqttClient.ConnectAsync(options);
Die Übergabe der Verbindungsoption des MQTT-Clients erfolgt direkt in der Methode. Der asynchrone Aufruf verhindert eine Blockierung beim Veröffentlichen von Nachrichten.
Topics abonnieren
Ist die Verbindung mit dem Broker erfolgreich, können Sie über die Methode SubscribeAsync(topic) ein MQTT-Thema abonnieren, um dann die gewünschten Nachrichten zu empfangen. Listing 2 zeigt die Implementierung für das Abonnieren von Nachrichten (Topics).Listing 2: Abonnieren von MQTT-Nachrichten
<span class="hljs-built_in">if</span> (connectResult.ResultCode == <br/> MqttClientConnectResultCode.Success)<br/>{<br/> <span class="hljs-built_in">Console</span>.WriteLine(<br/> <span class="hljs-string">"Verbindung zum Broker erfolgreich."</span>); <br/> await mqttClient.SubscribeAsync(topic);<br/> <br/> mqttClient.ApplicationMessageReceivedAsync += e =&gt;<br/> {<br/> <span class="hljs-built_in">Console</span>.WriteLine($<span class="hljs-string">"Empfangene Nachricht: "</span> + <br/> <span class="hljs-string">"{Encoding.UTF8.GetString("</span> +<br/> <span class="hljs-string">"e.ApplicationMessage.PayloadSegment)}"</span>);<br/> <span class="hljs-built_in">return</span> <span class="hljs-built_in">Task</span>.CompletedTask;<br/> };<br/>}
In der ersten Zeile im Listing 2 wird in der if-Abfrage die Verbindung zum Broker überprüft. Ist die Verbindung erfolgreich, wird das Thema abonniert und die empfangene Nachricht über die Callback-Funktion zurückgegeben.Um sich bei einem Topic wieder abzumelden, rufen Sie die Methode UnsubscribeAsync wie folgt auf:
await mqttClient.UnsubscribeAsync(topic)<span class="hljs-comment">;</span>
Nachrichten veröffentlichen
Um als Publisher Nachrichten an den Broker zu senden, verwenden Sie einfach die PublishAsync-Methode des MQTT-Clients. Listing 3 zeigt die simple Implementierung zum Veröffentlichen einer Nachricht. Das Senden im Beispiel erfolgt in einer Schleife, wobei alle zehn Sekunden eine Nachricht gesendet wird.Listing 3: Umsetzung der Methode PublishAsync
for (int i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++)<br/>{<br/> <span class="hljs-keyword">var</span> message = <span class="hljs-function"><span class="hljs-keyword">new</span> <span class="hljs-title">MqttApplicationMessageBuilder</span>()</span><br/><span class="hljs-function"> .<span class="hljs-title">WithTopic</span>(topic)</span><br/><span class="hljs-function"> .<span class="hljs-title">WithPayload</span>($"<span class="hljs-type">Hallo</span>, <span class="hljs-type">Welt</span>! <span class="hljs-type">Das</span> ist " +</span><br/><span class="hljs-function"> "<span class="hljs-type">Nachricht</span> <span class="hljs-type">Nummer</span>: {i}")</span><br/><span class="hljs-function"> .<span class="hljs-title">WithQualityOfServiceLevel</span>(</span><br/><span class="hljs-function"> <span class="hljs-type">MqttQualityOfServiceLevel</span>.<span class="hljs-type">AtLeastOnce</span>)</span><br/><span class="hljs-function"> .<span class="hljs-title">WithRetainFlag</span>()</span><br/><span class="hljs-function"> .<span class="hljs-title">Build</span>();</span><br/><span class="hljs-function"> <span class="hljs-title">await</span> <span class="hljs-title">mqttClient</span>.<span class="hljs-title">PublishAsync</span>(message);</span><br/><span class="hljs-function"> <span class="hljs-title">await</span> <span class="hljs-title">Task</span>.<span class="hljs-title">Delay</span>(<span class="hljs-number">10000</span>); </span><br/><span class="hljs-function">}</span>
Verbindung trennen
Wird der Client nicht mehr benötigt, sollte die Verbindung zum MQTT-Broker auch ordnungsgemäß getrennt werden. Um die Verbindung zum Client zu trennen, rufen Sie die DisconnectAsync-Methode des MQTT-Client-Objekts auf:
await mqttClient.DisconnectAsync()<span class="hljs-comment">;</span>
Fazit
Über offene Bibliotheken und Frameworks im IoT-Umfeld lassen sich viele nützliche Helfer in C#-Applikationen integrieren. Die gezeigte Implementierung orientiert sich am Standard. Somit lässt sie sich auch ohne Weiteres auf andere Libraries und Frameworks übertragen; Sie brauchen dazu lediglich die Methoden individuell anzusprechen beziehungsweise zu implementieren.Fussnoten
- MQTTnet Server/Broker, http://www.dotnetpro.de/SL2403MQTT1
- Mosquitto Broker, https://mosquitto.org
- HiveMQ, http://www.hivemq.com
- MQTTnet, http://www.dotnetpro.de/SL2403MQTT2