Tutorials

Dateien und Bilder über MQTT senden: Chunking, Base64 und Zusammensetzen

Dateien und Bilder über MQTT senden: Binär vs. Base64, Größenlimits des Brokers, Chunking mit Manifest und Zusammensetzen mit SHA-256-Prüfung in Python-Code.

Aktualisiert · 9 Min. Lesezeit

Dateien und Bilder über MQTT senden: Chunking, Base64 und Zusammensetzen

Ja, du kannst Dateien und Bilder über MQTT senden – ein Payload besteht einfach aus Bytes, ein JPEG oder eine Logdatei lässt sich also genau so publishen, wie sie auf der Platte liegt. Die Arbeit steckt im Detail: unter dem Größenlimit des Brokers bleiben, größere Dateien in Chunks aufteilen, sie in der richtigen Reihenfolge wieder zusammensetzen und nachweisen, dass das Ergebnis mit dem Original identisch ist.

Dieses Tutorial zeigt, wann du rohe Binärdaten sendest und wann Base64 sinnvoll ist, ein einfaches Chunking-Protokoll mit Manifest sowie einen funktionierenden Sender und Empfänger in Python, die du in wenigen Minuten testen kannst.

Binär oder Base64?

MQTT ist es egal, was im Payload steckt. Du musst ein Bild vor dem Publishen nicht kodieren; alle gängigen Client-Bibliotheken akzeptieren rohe Bytes (bytes in Python, Buffer / Uint8Array in MQTT.js, byte[] in Java und .NET).

Base64 ist nur dann eine Option, wenn die Bytes in einem Textdokument stecken müssen:

AnsatzOverheadVerwenden, wenn
Roher binärer Payload0 %Standard. Der Empfänger weiß, dass das Topic eine Datei transportiert.
Base64 in JSONetwa 33 % plus JSON-SchlüsselEin Consumer akzeptiert nur Text (manche Low-Code-Tools, Webhook-Bridges, Log-Pipelines).
Binärer Payload + MQTT-5-Propertieswenige BytesDu willst Metadaten (Dateiname, MIME-Typ), ohne die Bytes einzupacken.

Ein 600-KB-Foto wird Base64-kodiert zu rund 800 KB, und der Empfänger muss JSON parsen und dekodieren, bevor er es nutzen kann. Wenn du beide Enden kontrollierst, sende binär.

Mit MQTT 5 kannst du Metadaten an einen binären Payload hängen, statt ihn einzupacken: Setze die Property Content Type auf image/jpeg und lege den Dateinamen in eine User Property. Lass den Payload Format Indicator auf 0 (unspezifizierte Bytes); der Wert 1 teilt Broker und Empfänger mit, dass der Payload UTF-8-Text ist – und ein JPEG ist das nicht.

Kenne deine Größenlimits

Die MQTT-Spezifikation erlaubt eine Remaining Length von bis zu 268.435.455 Bytes, also etwa 256 MB pro Paket. In der Praxis kommt niemand in die Nähe, weil Broker und Dienste ihr eigenes Maximum durchsetzen:

Broker oder DienstTypisches Limit
MosquittoStandardmäßig kein Limit; einstellbar mit max_packet_size / message_size_limit
EMQXStandardmäßig 1 MB (mqtt.max_packet_size), konfigurierbar
HiveMQKonfigurierbares max-packet-size, Standard nahe dem Protokollmaximum
AWS IoT Core128 KB pro Nachricht
Azure IoT Hub256 KB pro Device-to-Cloud-Nachricht

Limits ändern sich zwischen Versionen und Tarifen, nimm die Tabelle also als Ausgangspunkt und prüfe deinen eigenen Broker. Unter MQTT 5 teilt der Server sein Limit in der CONNACK-Property Maximum Packet Size mit; ein Publish, das es überschreitet, endet typischerweise mit einem Disconnect mit Reason Code 0x95 (Packet too large). MQTT-3.1.1-Broker schließen meist einfach die Verbindung, was wie ein zufälliger Verbindungsabbruch aussieht. Der Guide zu Packet too large geht diese Symptome durch, und der Paketgrößen-Rechner zeigt, wie viel Topic und Properties zur Payload-Größe beitragen.

Große Nachrichten schaden auch auf weniger offensichtliche Weise: Sie blockieren die TCP-Verbindung, solange sie unterwegs sind (Keep-Alive-Pings und andere Nachrichten warten dahinter), der Broker muss sie für jede Offline-Session mit ihnen in der Queue im Speicher halten, und auf einer verlustbehafteten Verbindung wird ein einziges fehlgeschlagenes 5-MB-Publish komplett neu übertragen.

Ein einfaches Chunking-Protokoll

Alles, was größer ist als eine bequeme Einzelnachricht (eine gute Faustregel sind 64–256 KB), teilst du in Chunks auf. Halte das Protokoll langweilig:

  1. Manifest auf testmqtt/files/<device>/<file_id>/meta – JSON mit Dateiname, Gesamtgröße, Chunk-Größe, Anzahl der Chunks und einem SHA-256-Hash.
  2. Chunks auf testmqtt/files/<device>/<file_id>/chunk/<index> – rohe Bytes, keine Kodierung.
  3. Der Empfänger sammelt die Chunks nach Index, fügt sie zusammen, sobald alle da sind, und vergleicht den Hash.

Warum der Index im Topic? MQTT garantiert die Reihenfolge nur pro Topic und Publisher, Chunks auf verschiedenen Topics können also in beliebiger Reihenfolge ankommen. Mit QoS 1 kann ein Chunk außerdem doppelt eintreffen – speicherst du die Chunks in einer Map mit dem Index als Schlüssel, sind Duplikate harmlos. Der Index im Topic hält den Payload frei von allem außer Dateidaten; alternativ kannst du jedem Chunk einen 4-Byte-Binärheader voranstellen, wenn du ein einziges Topic pro Datei bevorzugst.

Nutze QoS 1 für Chunks. QoS 0 verliert in einem schlechten Netz früher oder später einen, und eine Datei mit einem fehlenden Chunk ist wertlos. QoS 2 kostet pro Chunk einen zusätzlichen Roundtrip ohne großen Nutzen, weil der Empfänger ohnehin dedupliziert.

Der Sender (Python)

Installiere den Client mit pip install "paho-mqtt>=2". Der Sender wartet auf jedes PUBACK, bevor er den nächsten Chunk schickt. Das ergibt eine natürliche Flusskontrolle und verhindert, dass das Inflight-Fenster des Brokers überläuft:

send_file.py
import hashlib, json, math, os, sys, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
CHUNK_SIZE = 32 * 1024                      # 32 KB pro Nachricht
path = sys.argv[1]

with open(path, "rb") as f:
    data = f.read()

file_id = uuid.uuid4().hex[:12]
base = f"testmqtt/files/demo/{file_id}"
total = math.ceil(len(data) / CHUNK_SIZE)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"sender-{file_id}")
# client.username_pw_set("username", "password")
client.connect(HOST, PORT)
client.loop_start()

manifest = {
    "name": os.path.basename(path),
    "size": len(data),
    "chunk_size": CHUNK_SIZE,
    "chunks": total,
    "sha256": hashlib.sha256(data).hexdigest(),
}
client.publish(f"{base}/meta", json.dumps(manifest), qos=1).wait_for_publish(10)

for i in range(total):
    chunk = data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]
    info = client.publish(f"{base}/chunk/{i}", chunk, qos=1)
    info.wait_for_publish(10)               # auf PUBACK warten
    time.sleep(0.02)                        # deutlich unter geteilten Rate Limits

print(f"{manifest['name']} in {total} Chunks gesendet, ID {file_id}")
client.loop_stop()
client.disconnect()

Der Empfänger (Python)

Der Empfänger abonniert alle Dateien des Geräts, speichert Chunks beim Eintreffen und schreibt die Datei, sobald der Hash stimmt:

receive_files.py
import hashlib, json
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
transfers = {}                              # file_id -> {"meta": ..., "chunks": {}}

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe("testmqtt/files/demo/+/#", qos=1)

def on_message(client, userdata, msg):
    parts = msg.topic.split("/")            # testmqtt/files/demo/<id>/meta|chunk/<n>
    file_id, kind = parts[3], parts[4]
    t = transfers.setdefault(file_id, {"meta": None, "chunks": {}})

    if kind == "meta":
        t["meta"] = json.loads(msg.payload)
    elif kind == "chunk":
        t["chunks"][int(parts[5])] = msg.payload   # Duplikate überschreiben einfach

    meta = t["meta"]
    if meta and len(t["chunks"]) == meta["chunks"]:
        blob = b"".join(t["chunks"][i] for i in range(meta["chunks"]))
        if hashlib.sha256(blob).hexdigest() == meta["sha256"]:
            with open("received-" + meta["name"], "wb") as f:
                f.write(blob)
            print(f"OK  {meta['name']} ({len(blob)} Bytes)")
        else:
            print(f"FEHLER {meta['name']}: Hash stimmt nicht")
        del transfers[file_id]

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
client.on_message = on_message
client.connect(HOST, PORT)
client.loop_forever()

Chunks können vor dem Manifest ankommen; deshalb läuft die Vollständigkeitsprüfung nach jeder Nachricht und nicht nur nach dem Manifest. Ergänze in Produktion einen Timeout, der unvollständige Übertragungen nach ein bis zwei Minuten verwirft – sonst führt ein verlorenes Manifest zu einem Speicherleck.

Die Übertragung testen

Starte den Empfänger in einem Terminal und den Sender in einem zweiten:

Roundtrip-Test
python receive_files.py
# zweites Terminal
python send_file.py photo.jpg
sha256sum photo.jpg received-photo.jpg     # macOS: shasum -a 256

Beide Hashes müssen übereinstimmen. Dann wird es härter: Sende eine Datei, deren Größe ein exaktes Vielfaches der Chunk-Größe ist (der klassische Off-by-one), sende eine leere Datei und kappe das Netz mitten in der Übertragung, um zu prüfen, dass der Empfänger eine unvollständige Datei meldet, statt Datenmüll zu schreiben. Während ein Test läuft, abonniere testmqtt/files/demo/+/meta im Online-MQTT-Client und sieh den Manifesten beim Eintreffen zu.

Für kleine Dateien in einer einzigen Nachricht reichen die Kommandozeilen-Clients von Mosquitto. -f publisht eine Datei als Payload, und -N verhindert, dass mosquitto_sub einen Zeilenumbruch anhängt, der die Binärdaten beschädigen würde:

Bild als Einzelnachricht mit mosquitto
mosquitto_sub -h broker.connectmq.com -t testmqtt/files/cli/snapshot -C 1 -N > out.jpg &
mosquitto_pub -h broker.connectmq.com -t testmqtt/files/cli/snapshot -q 1 -f snapshot.jpg

Weitere Kommandozeilen-Muster findest du in den Beispielen zu mosquitto_pub und mosquitto_sub. Um herauszufinden, ab welcher Größe dein Broker Nachrichten ablehnt, folge dem Test für große Payloads.

Ein Bild im Browser anzeigen

Ein Dashboard kann einen Kamera-Snapshot mit MQTT.js 5 direkt über Secure WebSockets anzeigen. Der Payload kommt als Bytes an, also packst du ihn in einen Blob:

snapshot.js
import mqtt from "mqtt";

const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt");
const img = document.querySelector("#snapshot");

client.on("connect", () => client.subscribe("testmqtt/files/cli/snapshot"));
client.on("message", (topic, payload) => {
  URL.revokeObjectURL(img.src);
  img.src = URL.createObjectURL(new Blob([payload], { type: "image/jpeg" }));
});

Halte Bilder für den Browser klein (einige zehn KB): Jeder abonnierte Tab lädt jedes Bild herunter.

Wann MQTT nicht das richtige Werkzeug ist

MQTT ist ein Messaging-Protokoll, kein Dateiserver. Für Firmware-Images, Videoclips oder alles über einige Megabyte ist das bessere Muster, die Datei in einen Object Storage hochzuladen und nur eine kleine Nachricht mit URL, Größe und Hash zu publishen. Das Gerät lädt per HTTPS herunter, das Wiederaufnahme und CDN-Caching unterstützt, und MQTT macht, was es gut kann: dem Gerät mitteilen, dass es etwas abzuholen gibt. OTA-Firmware-Updates über MQTT nutzen genau diese Aufteilung, und die Best Practices für Payload-Design zeigen, wie die Benachrichtigung schlank bleibt.

Dateiübertragungen im Online-Client verfolgen

Dateien über MQTT zu senden, läuft auf vier Regeln hinaus: rohe Bytes senden, unter dem Limit des Brokers bleiben, Chunks nummerieren und am Ende einen Hash prüfen. Baue zuerst den Roundtrip-Test, dann werden die Randfälle – Duplikate, Chunks in falscher Reihenfolge, ein verlorenes Manifest – leicht sichtbar und leicht zu beheben.

Häufig gestellte Fragen

Kann MQTT Binärdateien senden?

Ja. Ein MQTT-Payload ist eine beliebige Folge von Bytes, du kannst also ein JPEG, ein PDF oder ein Firmware-Image unverändert publishen. Base64 brauchst du nur, wenn der Payload in einem Textformat wie JSON transportiert werden muss.

Wie groß darf eine Datei bei MQTT maximal sein?

Das Protokoll erlaubt Pakete bis etwa 256 MB, Broker und Cloud-Dienste setzen aber meist deutlich niedrigere Limits, oft zwischen 128 KB und einigen MB. Prüfe die maximale Paketgröße deines Brokers und teile alles Größere in Chunks auf.

Sollte ich große Dateien überhaupt über MQTT senden?

Für Bilder und Dateien bis zu einigen Megabyte funktioniert die Übertragung in Chunks gut. Für große Firmware oder Videos publishst du über MQTT eine Download-URL und lässt das Gerät die Datei per HTTPS aus einem Object Storage laden.