In­halts­ver­zeich­nis

Das MQTT-Prinzip

Verbindungskontrolle (Connection Control)

Datenforderung (Subscription)

Datenversand (Publishing)

Quality of Service (QoS)

Des Vorgang des Publishing bei den verschiedenen QoS

Prozess-Sicht

Ereignis-Sicht

Die folgenden Angaben beziehen auf und wurden getestet mit dem MQTT-Broker Mosquitto Version 1.4.10 auf einem Raspberry Pi Zero.

Das MQTT-Prinzip

Message Queuing Telemetry Transport (MQTT) basiert auf dem Austausch von Nachrichten (Message). Es gibt insgesamt 14 verschiedene Paket-Typen (MQTT-Version 3.1). Sie dienen zur Verbindungskontrolle, zum Sunskription von Nachrichten und zu deren Versand.

Beim Datenversand (Publishing) sendet ein Teilnehmer (MQTT-Client, Publisher) an einem MQTT-Netzwerk Nachrichten mit (Mess-) Werten oder Ereignissen an einen Server (MQTT-Broker). Jede Nachricht besteht aus der Angabe eines Themas (Topic) und dem Nachrichteninhalt (Payload). Der Broker leitet diese Nachricht an alle verbunden Clients weiter, die das entsprechende Thema abonniert haben (Subcription). Der Publisher muss sich nicht darum kümmern, an wen er seine Nachrichten versenden soll und der Subscriber erhält nicht alle Nachrichten, sondern nur die zu den angeforderten Themen. Aufgabe des Brokers ist es, diese Vermittlung der Nachrichten sicher zu stellen.

Ein weiteres wichtiges Feature von MQTT sind "aufbewahrte Nachrichten" (retained messages). Standardmäßig werden Nachrichten vom Broker an die Clients gesandt und danach vergessen ("fire and forget"). Dieses Verfahren ist geeignet, Ereignisse zu melden. Zustände, z.B. die von Schaltern, können so nicht verwaltet werden. Clients, die sich neu beim Broker anmelden, würden erst dann Informationen über den Schalterzustand erhalten, wenn dieser seinen Zustand (erneut) mitteilt. Dies macht er aber typischerweise erst dann, wenn wenn sich der Zustand ändert. Der interessierte Client erfährt also ggf. erst sehr spät etwas über den Zustand des Schalters oder auch gar nicht. Bei retained Messages speichert der Broker den letzten Wert und sendet ihn an einen Client, wenn er das entsprechende Topic abonniert.

Bei einem ungeplanten Verbindungsabbruch können Clients über dieses Ereignis mit Hilfe eines "Testaments" (Last Will) informiert werden. Der Client muss dazu beim Anmelden festlegen, welche Nachricht versandt werden soll, wenn die Verbindung abbricht. Diese Nachricht kann auch als retained Message deklariert werden. (Hinweis: Das Testament wird nicht versandt, wenn die Verbindung von Client regulär per DISCONNECT-Nachricht beendet wird. Hier muss der Client selbst für den Versand geeigneter Meldungen sorgen.)

Üblicherweise erfolgt der Nachrichtenaustausch per Transmission Control Protocol (TCP). Bei TCP werden Datenverluste erkannt und automatisch behoben. Die Benutzung von TCP ist aber nicht zwingend vorgegeben. Der MQTT-Standard definiert lediglich den Aufbau der Nachrichten und die Reihenfolge des Versands.  Das User Datagram Protocol (UDP) wäre z.B. eine Transport-Alternative. Hier ist die Übertragung nicht abgesichert, u.a. können Pakete verloren gehen. Neben der Übertragung über IP-Netzwerke können selbstverständlich auch andere Medien eingesetzt werden, z.B. RS232. Um auch bei unsicheren Transportmedien zu funktionieren, erfordern die meisten Nachrichtentypen eine Rückmeldung des Empfängers (Handshake). Bleibt diese aus, wird i.d.R. die ursprüngliche Nachricht erneut versandt.

Die wichtigste Funktion von MQTT ist der Austausch von Daten. Hier ist das einfache Wiederholen von Nachrichten oft nicht zulässig. MQTT kennt deshalb drei verschiede Stufen zur Absicherung des Datenversands (Quality of Service, QoS): höchstens einmal (Stufe 0) - mindestens einmal (Stufe 1) - genau einmal (Stufe 2). Die Zuverlässigkeit nimmt mit wachsender Stufe zu, aber auch der Übertragungs- und der Programmaufwand. Insbesondere bei Embedded Devices mit begrenzten Ressourcen sollte man sich genau überlegen, welcher QoS sinnvoll ist.

Verbindungskontrolle (Connection Control)

Für die Verbindungskontrolle stehen fünf Nachrichtentypen zur Verfügung: Verbindungsanforderung (CONNECT), Verbindungsbestätigung (CONNACK), Verbindungsprüfung (PINGREQ, PINGRESP) und Verbindungsabbau (DISCONNECT).

Der Client sendet nach Aufbau des Transport-Kanals (z.B. Socket.Open bei TCP) eine CONNECT-Nachricht an den Broker. Dieser quittiert die Verbindungsanforderung mit einer CONNACK-Nachricht. Diese Nachricht teilt mit, ob die Verbindung akzeptiert wird oder welche Gründe dies verhindern. Bereits bei der Verbindungsanforderung wird der Last Will spezifiziert. Er wird also sofort wirksam.

Selbst bei TCP werden Verbindungsabbrüche nicht immer sauber erkannt. Häufig geschieht dies erst, wenn versucht wird, eine Nachricht über den unterbrochenen Kanal zu senden. Andere Verbindungsarten sind hier noch anfälliger. Wenn Broker oder Client über längere Zeit keine Nachrichten ausgetauscht haben (spezifiziert über den "Keep Alive"-Parameter), werden Verbindungstestnachrichten ausgetauscht. Wenn der Client erfahren möchte, ob der Broker noch verfügbar ist, sendet er eine PINGREQ-Nachricht (Ping Request). Der Broker antwortet mit einer PINGRESP-Nachricht (Ping Response). Klappt der Versand nicht oder antwortet der Broker nicht zeitgerecht, kann davon ausgegangen werden, dass die Verbindung unterbrochen wurde.

Der ordnungsgemäße Abbau der Verbindung erfolgt durch das Versenden einer DISCONNECT-Nachricht. Erkennt der Broker einen Verbindungsabbruch ohne vorher eine DISCONNECT-Nachricht erhalten zu haben, versendet er die "Last Will"-Nachricht.

Datenforderung (Subscription)

Ein MQTT-Client legt fest, zu welchen Themen (Topic) Nachrichten erhalten will. Dies geschieht über eine SUBSCRIBE-Nachricht. Der Client fordert hierbei auch die maximale QoS-Stufe für die einzelnen Themen an. Mit Hilfe von Wildcards ("+", "#") können auch Themenblöcke angefordert werden. Beim Mosquitto-Broker können über das Root-Topic "$SYS" Informationen über den Broker-Status abgerufen werden: Mosquitto man page, s. Abschnitt "Broker Status".

Der Broker quittiert die Anforderung mit einer SUBACK-Nachricht (Subscribe Acknowledgement). Hier teilt er mit, welche Topic-Angaben er akzeptiert und welchen maximalen QoS er liefern kann. Bei vollständigen Implementierungen des Standards ist dies i.d.R. der angeforderte Level. Fehler können nur bei einer unzulässigen Spezifikation des Topics auftreten, z.B. durch ungültige Kombination von Wildcards.

Subskriptionen werden über eine UNSUBSCRIBE-Nachricht aufgehoben. Der Brocker quittiert diese Nachricht mit einer UNSUBACK-Nachricht (Unsubscribe Acknowledgement).

Fallen die Quittungsnachrichten des Brokers aus, werden die SUBSCRIBE- oder UNSUBSCRIBE-Nachricht einfach wiederholt.

Datenversand (Publishing)

Der Datenaustausch ist die wichtigste Funktion des MQTT-Service. Die Zuverlässigkeit lässt sich sich bei den anderen Funktionalitäten durch einfaches Wiederholen der Nachrichten realisieren. Es ist z.B. unerheblich, ob ein Topic zweimal abonniert wird.

Je nach Anwendung ist das wiederholte Übertragen von Nutzdaten jedoch nicht zielführend. Sollen z.B. inkrementelle Werte versandt werden, die einen Kontostand erhöhen, führt der Mehrfachversand möglicherweise zu Chaos. Es kann durchaus sein, dass die ursprüngliche Nachricht erhalten und verarbeitet wurde, aber die Quittung verloren ging. In anderen Fällen, z.B. beim Versand von Anweisungen, ist die Zeitspanne zwischen Originalversand und Wiederholung ein Problem. In diesem Zeitraum hätte ein anderer Client eine gegenteilige Anweisung versenden können, die nun durch die Wiederholung aufgehoben würde.

Um diese Probleme zu umgehen, bietet das MQQT-Protokoll drei verschiede Service-Level an.

Quality of Service (QoS)

Die verschieden Stufen betreffen nur den Versand von Nachrichten des Typs PUBLISH. Alle anderen Nachrichtentypen besitzen keine verschiedene Service-Level.

Es gibt drei Service-Level:

Der Client legt beim Versenden einer SUBSCRIBE-Nachricht an der MQTT-Broker, mit welchem maximalen Service-Level er PUBSLISH-Nachrichten zu einem bestimmten Topic erhalten will. Wird ein Topic mehrfach abonniert, z.B. bei der Benutzung von Wildcards, gilt der höchste angegebene Level. Der Broker meldet per SUBACK-Nachricht zurück, welchen maximalen QoS er zusichern kann. I.d.R. ist dies der angeforderte Level. Broker, die nicht den kompletten Funktionsumfang des MQTT-Protokolls implementieren, können auch einen geringeren Level zurückmelden.

Der Broker leitet Nachrichten des Typs PUBLISH mit dem QoS-Level weiter, mit dem er sie erhalten hat. Wenn jedoch der zugesicherte Level (s.o.) kleiner ist, wird dieser Level verwandt. Beispiel:

Client A abonniert (SUBSCRIBE) Topic "t0" mit QoS = 0, Topic "t1" mit QoS = 1 und Topic "t2" mit QoS = 2.

Client B veröffentlicht (PUBLISH) alle drei Topics "t0", "t1" und Topic "t2" mit QoS = 1.

Client A erhält Topic "t0" mit QoS = 0 (Client A limitiert bei der Subsrikption), Topic "t1" mit QoS = 1 und Topic "t2" mit QoS = 1 (Client B limitiert beim Veröffentlichen).

Des Vorgang des Publishing bei den verschiedenen QoS

Je nach QoS laufen intern verschiedene Prozesse ab. Dabei ist es gleichgültig, ob ein Client an den Broker sendet oder der Broker an einen Client. Dabei werden nur die Rollen des Senders und des Empfängers getauscht.

Prozess-Sicht

QoS 0 - mindestens einmal (at most once)

Nachrichtenfluss QoS 0

Dies ist der niedrigste QoS-Level. Der Sender versendet eine Nachricht vom Typ PUBLISH und kümmert sich nicht weiter darum. Dies bedingt, dass die Auslieferung der Nachricht nicht garantiert ist.

QoS 1 - höchstens einmal (at least once)

Nachrichtenfluss QoS 1

Dieser QoS-Level garantiert, dass die Nachricht mindestens einmal empfangen wird. Der Übertragungsprozess beginnt damit, dass die Nachricht senderseitig gespeichert wird (Outbound-Queue). Danach wird die Nachricht versandt (Send PUBLISH). Der Sender wartet nun darauf, dass der Empfänger den Erhalt der PUBLISH-Nachricht mit einer Nachricht vom Typ PUBACK (Publish Ackknowledge) bestätigt. Erhält der Sender in der vorgegeben Zeit (Keep Alive) keine solche Rückmeldung, wird die ursprüngliche Nachricht, nun mit gesetztem DUP-Flag (Duplicate), noch einmal versandt. Dieser Vorgang wiederholt sich, bis eine entsprechende PUBACK-Nachricht erhalten wurde. Wenn eine passende PUBACK-Nachricht empfangen wurde, wird die Nachricht gelöscht und der Übertragungavorgang ist beendet.

Um PUBLISH- und PUBACK-Nachrichten einander zuordnen zu können, enthalten beide eine eindeutige Nachrichtennummer (Packet Identifier).

QoS 2 - genau einmal (exactly once)

Nachrichtenfluss QoS 2

Dieser QoS-Level garantiert, dass die Nachricht genau einmal empfangen wird. Auch hier dient eine eindeutige Nachrichtennummer (Packet Identifier) zur sicheren Identifikation der Nachrichten.

Der Übertragungsprozess beginnt damit, dass die Nachricht senderseitig gespeichert wird (Outbound-Queue). Danach wird die Nachricht versandt (Send PUBLISH). Der Sender wartet nun darauf, dass der Empfänger den Erhalt der PUBLISH-Nachricht mit einer Nachricht vom Typ PUBREC (Publish Received) bestätigt. Erhält der Sender in der vorgegeben Zeit (Keep Alive) keine solche Rückmeldung, wird die ursprüngliche Nachricht, nun mit gesetztem DUP-Flag (Duplicate), erneut versandt. Dieser Vorgang wiederholt sich, bis eine entsprechende PUBREC-Nachricht erhalten wurde.

Der Empfänger prüft zunächst, ob die Nachricht (identifizierbar an der eindeutigen Nachrichtennummer) bereits empfangen und ausgewertet wurde. Ist dies nicht der Fall, wird die Nachricht gespeichert (Inbound-Queue) und verarbeitet. Andernfalls wird die Nachricht ignoriert. In beiden Fällen antwortet der Empfänger mit einer PUBREC-Nachricht.

Erhält der Sender die passende PUBREC-Nachricht, löscht er die Nachricht aus der Outbound-Queue. Das wiederholte Senden der PUBLISH-Nachricht wird damit beendet. Über eine PUBREL-Nachricht (Publish Release) teilt er dem Empfänger mit, dass die Nachricht aus der Inbound-Queue gelöscht werden kann.

Der Sender wartet nun darauf, dass der Empfänger den Erhalt der PUBREL-Nachricht mit einer Nachricht vom Typ PUBCOMP (Publish Complete) bestätigt. Erhält der Sender in der vorgegeben Zeit (Keep Alive) keine solche Rückmeldung, wird die PUBREL-Nachricht erneut versandt. Dieser Vorgang wiederholt sich, bis eine entsprechende PUBCOMP-Nachricht erhalten wurde.

Erhält der Empfänger die PUBREL-Nachricht, löscht er die zugehörige PUBLISH-Nachricht aus seiner Inbound-Queue und bestätigt den Vorgang mit einer PUBCOMP-Nachricht.

Ereignis-Sicht

Der oben beschriebene Prozess ließe sich mit einem Zustandsautomaten für jede Nachricht implementieren. Einfacher ist es jedoch, sich zu überlegen, welche Ereignisse eintreten können und wie darauf zu reagieren ist. Dargestellt wird nur die Client-Sicht. Man muss jedoch beachten, dass der Client sowohl als Sender als auch als Empfänger fungieren kann.

In den nachfolgen Beschreibungen ist mit "Subscriber" der Programm-Teil gemeint, der die Nachrichten verarbeitet, also auf die Ereignisse reagiert. Die Grafiken und Beschreibungen enthalten nur die prozessrelevanten Komponenten. Elemente, die z.B. die Wiederholung der Handshake-Nachrichten ermöglichen, sind der Übersichtlichkeit halber ausgelassen worden.

 

Erhalt einer PUBLISH-Nachricht

Erhalt einer PUBLISH-Nachricht

Je nach Service-Level der empfangenen Nachricht sind verschiedene Aktionen notwendig.

QoS 0: Die Nachricht wird wird einfach nur verarbeitet.

QoS 1: Die Nachricht wird verarbeitet und mit einer PUBACK-Nachricht quittiert.

QoS 2: Es wird geprüft, ob die Nachricht schon verarbeitet wurde. Wenn nicht, wird sie gespeichert und verarbeitet. Der Erhalt der Nachricht wird mit einer PUBREC-Nachricht quittiert.

Erhalt einer PUBREC-Nachricht

Erhalt einer PUBREC-Nachricht

Eine PUBREC-Nachricht erhält der Client, wenn er selbst die PUBLISH-Nachricht versandt hat. Der Broker quittiert hiermit, dass er die Nachricht empfangen hat. Sie kann aus der Outbound-Queue gelöscht werden.

Erhalt einer PUBREL-Nachricht

Erhalt einer PUBREL-Nachricht

Eine PUBREL-Nachricht erhält der Client, wenn die Nachricht aus der Inbound-Queue gelöscht werden kann.

Ereignis Timeout

Timeout

Regelmäßig wird die Queue auf Nachrichten überprüft, bei denen eine Zeitüberschreitung für das Warten auf die Quittung vorliegt. Die Nachrichten werden dann erneut versandt.

Ein Timer regelt die notwendige Wiederholung des Versendens von Nachrichten. Alle Nachrichten, zu der eine Quittung erwartet wird, werden mit dem Zeitpunkt des Versands gespeichert (in der Outbound-Queue). Erhält der Client die passende Quittung, wird die Nachricht aus der Outbound-Queue gelöscht.