MQTT-Grundlagen

MQTT-Paketstruktur: Fixed Header, Variable Header und Payload

Ein Rundgang durch MQTT-Pakete auf Byte-Ebene: wie der Fixed Header Typ und Flags kodiert, wie die Remaining Length funktioniert und was Variable Header und Payload enthalten, anhand eines CONNECT und PUBLISH, die Sie mit Wireshark mitschneiden können.

Hex-Dump eines MQTT-PUBLISH, aufgeteilt in Fixed Header, Variable Header und Payload, zur Erklärung der MQTT-Paketstruktur
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Die MQTT-Paketstruktur ist bewusst kompakt: Jedes Paket beginnt mit einem Fixed Header von nur zwei Byte, gefolgt von einem optionalen Variable Header und einem optionalen Payload. Dieser Rundgang dekodiert echte CONNECT-, CONNACK- und PUBLISH-Pakete Byte für Byte, damit Sie einen Wireshark-Mitschnitt oder einen Hex-Dump sicher lesen und Kodierungsfehler in Ihren eigenen Clients erkennen können.

Die drei Teile jedes MQTT-Pakets

Jedes MQTT-Control-Paket folgt demselben Aufbau:

Teil Vorhanden in Inhalt
Fixed Header Allen Paketen Pakettyp, Flags, Remaining Length
Variable Header Den meisten Paketen Paketspezifische Felder wie Topic-Name, Packet Identifier, Protokollname und in MQTT 5 Properties
Payload Einigen Paketen Client ID und Zugangsdaten im CONNECT, Anwendungsnachricht im PUBLISH, Topic-Filter im SUBSCRIBE

Die kleinsten Pakete, PINGREQ, PINGRESP und ein DISCONNECT in MQTT 3.1.1, bestehen nur aus dem zwei Byte langen Fixed Header. Diese Sparsamkeit ist ein wesentlicher Grund, warum MQTT über langsame Verbindungen gut funktioniert, und deshalb hilft das Verständnis der Struktur, wenn Sie die MQTT-Bandbreitennutzung abschätzen.

Der Fixed Header

Das erste Byte teilt sich in zwei Hälften:

Bit:     7  6  5  4 | 3  2  1  0
         packet type | flags

Die oberen vier Bits enthalten den Pakettyp, von 1 (CONNECT) bis 15 (AUTH). Die unteren vier Bits sind Flags. Bei den meisten Paketen sind die Flags feste, reservierte Werte; nur PUBLISH nutzt sie inhaltlich:

Bit Bedeutung bei PUBLISH
3 DUP: erneute Zustellung einer QoS-1- oder QoS-2-Nachricht
2-1 QoS-Stufe: 00, 01 oder 10
0 RETAIN: Broker soll diese Nachricht speichern

Ein erstes Byte 0x30 ist also ein PUBLISH mit QoS 0, 0x32 eines mit QoS 1, 0x31 eine retained QoS-0-Nachricht und 0x3A eine erneute QoS-1-Zustellung mit gesetztem DUP. SUBSCRIBE, UNSUBSCRIBE und PUBREL müssen die Flags 0010 haben, weshalb sie als 0x82, 0xA2 und 0x62 erscheinen. Die Referenz der MQTT-Control-Pakete listet jeden Typ mit seinem ersten Byte auf.

Kodierung der Remaining Length

Nach dem ersten Byte folgt die Remaining Length: die Anzahl der Bytes von Variable Header plus Payload, ohne den Fixed Header selbst. Sie verwendet einen Variable Byte Integer von einem bis vier Byte. Jedes Byte trägt sieben Bit Nutzwert, und das oberste Bit signalisiert „es folgt ein weiteres Byte“.

Wertebereich Verwendete Bytes
0 bis 127 1
128 bis 16.383 2
16.384 bis 2.097.151 3
2.097.152 bis 268.435.455 4

Beispiel: eine Remaining Length von 321. Da 321 = 2 x 128 + 65 ist, lautet das erste Byte 65 mit gesetztem Fortsetzungsbit (0xC1) und das zweite Byte 2 (0x02). Aus dem Maximum von vier Byte ergibt sich die oft zitierte Paketobergrenze des Protokolls von 256 MB; der Leitfaden zur maximalen MQTT-Nachrichtengröße erklärt, warum reale Broker deutlich niedrigere Limits setzen.

Hier ein Decoder, den Sie beim Lesen von Hex-Dumps verwenden können:

def decode_remaining_length(data: bytes, offset: int = 1):
    multiplier, value = 1, 0
    for i in range(4):
        byte = data[offset + i]
        value += (byte & 0x7F) * multiplier
        if byte & 0x80 == 0:
            return value, i + 1  # Wert, Anzahl verwendeter Bytes
        multiplier *= 128
    raise ValueError("Malformed remaining length")

print(decode_remaining_length(bytes([0x30, 0xC1, 0x02])))  # (321, 2)

Eine echte Session Schritt für Schritt

Führen Sie diesen Befehl aus, um eine Nachricht per MQTT 3.1.1, das mosquitto_pub standardmäßig verwendet, an den öffentlichen MQTT-Broker zu senden, der für Tests und Prototypen gedacht ist:

mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-a1b2 \
  -t "connectmq/demo/a1b2/t" -m "21.5" -d

Port 1883 ist unverschlüsselt, ein Paketmitschnitt zeigt also jedes Byte. Zeichnen Sie in Wireshark auf Ihrer Netzwerkschnittstelle auf und wenden Sie den Anzeigefilter mqtt an. Wireshark dekodiert Port 1883 automatisch als MQTT; Sie sehen CONNECT, CONNACK, PUBLISH und DISCONNECT. Verwenden Sie ein eigenes zufälliges Topic-Segment und denken Sie daran, dass alles auf dem öffentlichen MQTT-Broker für andere lesbar ist.

CONNECT

Das erste Paket des Clients umfasst 23 Byte:

10 15                         Fixed Header: Typ 1 (CONNECT), Remaining Length 21
00 04 4D 51 54 54             Protokollname: Länge 4, "MQTT"
04                            Protokoll-Level: 4 = MQTT 3.1.1
02                            Connect Flags: Clean Session
00 3C                         Keep Alive: 60 Sekunden
00 09 64 65 6D 6F 2D 61 31 62 32   Payload: Client-ID-Länge 9, "demo-a1b2"

Das Byte der Connect Flags verdient einen genaueren Blick. Bit 1 ist Clean Session, Bit 2 Will Flag, Bits 3-4 Will QoS, Bit 5 Will Retain, Bit 6 Password Flag und Bit 7 User Name Flag. Ein Client, der sich mit Benutzername, Passwort und Clean Session verbindet, würde 0xC2 senden. Sind diese Flags gesetzt, folgen Benutzername und Passwort im Payload auf die Client ID, jeweils mit einem zwei Byte langen Längenpräfix. Das ist ein Grund, mit echten Zugangsdaten immer TLS auf Port 8883 zu verwenden: Auf Port 1883 werden sie im Klartext übertragen.

CONNACK

Der Broker antwortet mit vier Byte:

20 02 00 00

Typ 2, Remaining Length 2, Session Present 0, Rückgabecode 0 (angenommen). Jedes andere letzte Byte ist eine Ablehnung; der Leitfaden zu den MQTT-CONNACK-Rückgabecodes dekodiert jeden Wert.

PUBLISH

Die Nachricht selbst umfasst 29 Byte:

30 1B                         Fixed Header: PUBLISH, QoS 0, kein Retain; Remaining Length 27
00 15                         Topic-Länge: 21
63 6F 6E 6E 65 63 74 6D 71 2F 64 65 6D 6F 2F 61 31 62 32 2F 74
                              Topic: "connectmq/demo/a1b2/t"
32 31 2E 35                   Payload: "21.5"

Beachten Sie, was fehlt: Es gibt keinen Packet Identifier, weil QoS-0-Nachrichten nie bestätigt werden. Mit -q 1 wird das erste Byte zu 0x32, nach dem Topic erscheinen zwei Byte Packet Identifier, und der Broker antwortet mit einem vier Byte langen PUBACK (40 02 plus Identifier). Es gibt auch kein Feld für die Payload-Länge; der Payload ist einfach alles, was laut Remaining Length nach dem Variable Header übrig bleibt.

Das Beispiel zeigt auch, wie viel der Topic-Name kostet: 21 Byte Topic stehen 4 Byte Payload gegenüber. In Industrie-4.0-Anlagen mit einem Unified Namespace (UNS) nach ISA-95 sind Topics wie acme/werk-stuttgart/montage/linie-1/zelle-3/temperatur noch länger, sodass der Topic-Name bei kleinen Messwerten oft den größten Teil des Pakets ausmacht. Topic Aliases aus MQTT 5 setzen genau hier an.

DISCONNECT

Zum Schluss sendet der Client E0 00: Typ 14, Remaining Length 0. In MQTT 3.1.1 ist das das gesamte Paket.

Was sich in MQTT 5 ändert

MQTT 5 behält dasselbe Grundgerüst bei, ergänzt aber den Variable Header der meisten Pakete um Properties. Ein Properties-Abschnitt beginnt mit einer eigenen Länge als Variable Byte Integer, gefolgt von Paaren aus Kennung und Wert. Selbst ohne Properties ist das Längenbyte 00 vorhanden.

Für dieselbe Session über MQTT 5 gilt:

  • CONNECT hat das Protokoll-Level 05 und nach dem Keep Alive eine Properties-Länge.
  • CONNACK wächst um einen Reason Code und Properties, die oft Limits wie Maximum Packet Size, Topic Alias Maximum oder Maximum QoS ankündigen.
  • PUBLISH hat nach dem Topic (und dem Packet Identifier) eine Properties-Länge, sodass ein QoS-0-Publish ohne Properties ein Byte größer ist.
  • DISCONNECT kann einen Reason Code und Properties tragen; so erklärt der Broker, warum er eine Verbindung geschlossen hat.

Properties wie Topic Alias, Message Expiry Interval und User Properties ermöglichen die meisten Funktionen von MQTT 5.

Häufige Kodierungsfehler und wie Sie sie erkennen

Wenn Sie einen eigenen Client schreiben oder MQTT auf einem Gateway parsen, etwa auf einem Edge-Gerät neben einer SPS, prüfen Sie zuerst Folgendes:

  1. Remaining Length als festes Zwei-Byte-Feld geschrieben. Funktioniert bei kleinen Paketen, bricht oberhalb von 127 Byte.
  2. String-Längen in Zeichen statt in Bytes gezählt. Topic-Namen mit Nicht-ASCII-Zeichen haben mehr Bytes als Zeichen, was fehlerhafte Pakete erzeugt.
  3. Fehlender Packet Identifier bei QoS 1 oder 2. Der Broker liest einen Teil Ihres Payloads als Identifier, das Topic ist aber korrekt, sodass der Fehler wie beschädigte Daten aussieht.
  4. Falsche reservierte Flags bei SUBSCRIBE oder PUBREL, die Broker als fehlerhaft behandeln müssen, woraufhin sie die Verbindung schließen.
  5. Vergessene leere Properties-Länge beim Umstellen eines Clients auf MQTT 5.

Wireshark markiert fehlerhafte Pakete deutlich, und ein MQTT-5-Broker antwortet im DISCONNECT mit dem Reason Code 0x81 Malformed Packet, was sich viel schneller diagnostizieren lässt als ein stilles Schließen. Die maßgeblichen Definitionen finden Sie in der OASIS-MQTT-Spezifikation.

Pakete gegen einen echten ConnectMQ-Broker untersuchen

Pakete zu lesen ist der schnellste Weg, MQTT zu verstehen, und das MQTT-Tutorial baut mit Sessions, QoS und Retained Messages auf diesem Fundament auf. ConnectMQ betreibt den öffentlichen MQTT-Broker unter broker.connectmq.com genau für diese Art von Experiment und private Broker auf TLS-Port 8883, sobald echte Daten im Spiel sind.

Vom Paketmitschnitt zur Produktion mit ConnectMQ

Wenn Ihr Client bereit ist, bietet Ihnen ein verwalteter MQTT-Broker verschlüsselte Endpunkte, ausgegebene Zugangsdaten und ein Portal mit browserbasiertem Client und Live-Topic-Explorer. Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ganz ohne Kreditkarte, oder lesen Sie mehr zur Wahl eines Cloud-MQTT-Brokers.

Häufig gestellte Fragen

Wie groß ist das kleinste MQTT-Paket?

Zwei Byte. PINGREQ, PINGRESP und ein DISCONNECT in MQTT 3.1.1 bestehen nur aus dem Fixed Header: einem Byte für Typ und Flags und einem Byte Remaining Length mit dem Wert null.

Was umfasst die Remaining Length?

Sie zählt die Bytes von Variable Header und Payload, nicht aber den Fixed Header selbst. Sie wird als Variable Byte Integer von einem bis vier Byte kodiert.

Warum kann ich mein Passwort in Wireshark lesen?

Weil Port 1883 unverschlüsselt ist und CONNECT Benutzername und Passwort im Payload überträgt. Verwenden Sie für echte Zugangsdaten TLS auf Port 8883 oder sichere WebSockets auf 8084.

Enthält das PUBLISH-Paket die Payload-Länge?

Nein. Die Payload-Länge ergibt sich implizit: Sie ist die Remaining Length abzüglich der Größe des Variable Headers, der den Topic-Namen, bei QoS 1 und 2 den Packet Identifier und in MQTT 5 die Properties enthält.

Verwandte Artikel

Wer hat MQTT erfunden? Die Geschichte von Ölpipelines bis zum IoT

MQTT wurde 1999 von Andy Stanford-Clark (IBM) und Arlen Nipper (Arcom) erfunden, um Ölpipelines per Satellit zu überwachen. Verfolgen Sie den Weg zu Open Source, OASIS, ISO und den heutigen verwalteten MQTT-Brokern in der Cloud.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.