Tutoriels

Envoyer des fichiers et des images via MQTT : découpage, base64 et réassemblage

Envoyer des fichiers et images via MQTT : binaire ou base64, limites de taille du broker, découpage avec manifeste et réassemblage vérifié par SHA-256.

Mis à jour · 9 min de lecture

Envoyer des fichiers et des images via MQTT : découpage, base64 et réassemblage

Oui, vous pouvez envoyer des fichiers et des images via MQTT : un payload n'est qu'une suite d'octets, donc un JPEG ou un fichier de log se publie exactement tel qu'il est sur le disque. Le travail est dans les détails : rester sous la limite de taille du broker, découper les gros fichiers en chunks, les remettre dans le bon ordre et prouver que le résultat est identique à l'original.

Ce tutoriel explique quand envoyer du binaire brut et quand base64 a du sens, présente un protocole de découpage simple avec un manifeste, ainsi qu'un émetteur et un récepteur Python fonctionnels que vous pouvez tester en quelques minutes.

Binaire ou base64 ?

MQTT se moque de ce que contient le payload. Inutile d'encoder une image avant de la publier : toutes les bibliothèques clientes courantes acceptent des octets bruts (bytes en Python, Buffer / Uint8Array dans MQTT.js, byte[] en Java et .NET).

Base64 ne se justifie que si les octets doivent vivre à l'intérieur d'un document texte :

ApprocheSurcoûtÀ utiliser quand
Payload binaire brut0 %Par défaut. Le récepteur sait que le topic transporte un fichier.
Base64 dans du JSONenviron 33 % plus les clés JSONUn consommateur n'accepte que du texte (certains outils low-code, passerelles webhook, pipelines de logs).
Payload binaire + propriétés MQTT 5quelques octetsVous voulez des métadonnées (nom de fichier, type MIME) sans envelopper les octets.

Une photo de 600 Ko devient environ 800 Ko une fois encodée en base64, et le récepteur doit parser le JSON et décoder avant de pouvoir l'utiliser. Si vous maîtrisez les deux extrémités, envoyez du binaire.

Avec MQTT 5, vous pouvez attacher des métadonnées à un payload binaire au lieu de l'envelopper : définissez la propriété Content Type à image/jpeg et placez le nom du fichier dans une user property. Laissez le Payload Format Indicator à 0 (octets non spécifiés) ; le mettre à 1 indique au broker et au récepteur que le payload est du texte UTF-8, ce qu'un JPEG n'est pas.

Connaître ses limites de taille

La spécification MQTT autorise une remaining length jusqu'à 268 435 455 octets, soit environ 256 Mo par paquet. En pratique, personne n'en approche, car les brokers et services imposent leur propre maximum :

Broker ou serviceLimite typique
MosquittoAucune limite par défaut ; réglable avec max_packet_size / message_size_limit
EMQX1 Mo par défaut (mqtt.max_packet_size), configurable
HiveMQmax-packet-size configurable, valeur par défaut proche du maximum du protocole
AWS IoT Core128 Ko par message
Azure IoT Hub256 Ko par message appareil-vers-cloud

Les limites évoluent selon les versions et les offres : prenez ce tableau comme point de départ et vérifiez sur votre propre broker. En MQTT 5, le serveur annonce sa limite dans la propriété Maximum Packet Size du CONNACK ; un publish qui la dépasse se termine généralement par une déconnexion avec le reason code 0x95 (Packet too large). Les brokers MQTT 3.1.1 ferment souvent la connexion sans explication, ce qui ressemble à une déconnexion aléatoire. Le guide Packet too large passe ces symptômes en revue, et le calculateur de taille de paquet montre ce que le topic et les propriétés ajoutent à la taille du payload.

Les gros messages pénalisent aussi de façon moins visible : ils bloquent la connexion TCP pendant leur transit (les pings keep-alive et les autres messages attendent derrière), le broker doit les garder en mémoire pour chaque session hors ligne qui les a en file d'attente et, sur un lien instable, un seul publish de 5 Mo raté est retransmis en entier.

Un protocole de découpage simple

Au-delà d'un message unique confortable (une bonne règle : 64–256 Ko), découpez le fichier en chunks. Gardez un protocole sans surprise :

  1. Manifeste sur testmqtt/files/<device>/<file_id>/meta : JSON avec nom du fichier, taille totale, taille de chunk, nombre de chunks et un hash SHA-256.
  2. Chunks sur testmqtt/files/<device>/<file_id>/chunk/<index> : octets bruts, sans encodage.
  3. Le récepteur collecte les chunks par index et, une fois tous reçus, les assemble et compare le hash.

Pourquoi l'index dans le topic ? MQTT ne garantit l'ordre que par topic et par émetteur, donc des chunks sur des topics différents peuvent arriver dans n'importe quel ordre. En QoS 1, un chunk peut aussi arriver deux fois : stocker les chunks dans une table indexée rend les doublons inoffensifs. Mettre l'index dans le topic laisse le payload ne contenir que les données du fichier ; vous pouvez aussi préfixer chaque chunk d'un en-tête binaire de 4 octets si vous préférez un seul topic par fichier.

Utilisez QoS 1 pour les chunks. QoS 0 finira par en perdre un sur un mauvais réseau, et un fichier auquel il manque un chunk est inutilisable. QoS 2 ajoute un aller-retour par chunk sans grand bénéfice, puisque le récepteur dédoublonne déjà.

L'émetteur (Python)

Installez le client avec pip install "paho-mqtt>=2". L'émetteur attend chaque PUBACK avant d'envoyer le chunk suivant, ce qui fournit un contrôle de flux naturel et évite de saturer la fenêtre inflight du broker :

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 Ko par message
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)               # attendre le PUBACK
    time.sleep(0.02)                        # bien en dessous des limites partagées

print(f"{manifest['name']} envoyé en {total} chunks, id {file_id}")
client.loop_stop()
client.disconnect()

Le récepteur (Python)

Le récepteur s'abonne à tous les fichiers de l'appareil, stocke les chunks à leur arrivée et écrit le fichier quand le hash correspond :

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   # les doublons écrasent simplement

    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)} octets)")
        else:
            print(f"KO  {meta['name']} : hash différent")
        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()

Des chunks peuvent arriver avant le manifeste : c'est pourquoi la vérification de complétude s'exécute après chaque message et pas seulement après le manifeste. En production, ajoutez un timeout qui abandonne les transferts incomplets au bout d'une ou deux minutes, sinon un manifeste perdu provoque une fuite mémoire.

Tester le transfert

Lancez le récepteur dans un terminal et l'émetteur dans un autre :

Test aller-retour
python receive_files.py
# second terminal
python send_file.py photo.jpg
sha256sum photo.jpg received-photo.jpg     # macOS : shasum -a 256

Les deux hash doivent correspondre. Corsez ensuite le test : envoyez un fichier dont la taille est un multiple exact de la taille de chunk (le classique off-by-one), envoyez un fichier vide et coupez le réseau en plein transfert pour confirmer que le récepteur signale un fichier incomplet au lieu d'écrire n'importe quoi. Pendant un test, abonnez-vous à testmqtt/files/demo/+/meta dans le client MQTT en ligne pour voir arriver les manifestes.

Pour de petits fichiers tenant dans un seul message, les clients en ligne de commande de Mosquitto suffisent. -f publie un fichier comme payload, et -N empêche mosquitto_sub d'ajouter un saut de ligne qui corromprait le binaire :

Image en un seul message avec 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

Vous trouverez d'autres usages de la ligne de commande dans les exemples mosquitto_pub et mosquitto_sub. Pour savoir à partir de quelle taille votre broker rejette les messages, suivez le test de gros payloads.

Afficher une image dans le navigateur

Un dashboard peut afficher une capture de caméra directement depuis MQTT via WebSockets sécurisés avec MQTT.js 5. Le payload arrive sous forme d'octets, il suffit de l'envelopper dans un 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" }));
});

Gardez les images destinées au navigateur petites (quelques dizaines de Ko) : chaque onglet abonné télécharge chaque image.

Quand ne pas utiliser MQTT pour des fichiers

MQTT est un protocole de messagerie, pas un serveur de fichiers. Pour des images de firmware, des clips vidéo ou tout ce qui dépasse quelques mégaoctets, le meilleur schéma consiste à déposer le fichier dans un stockage objet et à ne publier qu'un petit message avec l'URL, la taille et le hash. L'appareil télécharge en HTTPS, qui gère la reprise et le cache CDN, et MQTT fait ce qu'il fait de mieux : prévenir l'appareil qu'il y a quelque chose à récupérer. Les mises à jour OTA de firmware via MQTT reposent exactement sur ce partage, et les bonnes pratiques de conception de payload expliquent comment garder le message de notification léger.

Suivre les transferts dans le client en ligne

Envoyer des fichiers via MQTT se résume à quatre règles : envoyer des octets bruts, rester sous la limite du broker, numéroter les chunks et vérifier un hash à la fin. Construisez d'abord le test aller-retour, et les cas limites (doublons, chunks dans le désordre, manifeste perdu) deviennent faciles à repérer et à corriger.

Questions fréquentes

MQTT peut-il envoyer des fichiers binaires ?

Oui. Un payload MQTT est une suite arbitraire d'octets : vous pouvez publier un JPEG, un PDF ou une image de firmware tels quels. Base64 n'est nécessaire que si le payload doit voyager dans un format texte comme JSON.

Quelle est la taille maximale d'un fichier avec MQTT ?

Le protocole autorise des paquets d'environ 256 Mo, mais les brokers et services cloud imposent généralement des limites bien plus basses, souvent entre 128 Ko et quelques Mo. Vérifiez la taille maximale de paquet de votre broker et découpez tout ce qui la dépasse.

Faut-il vraiment envoyer de gros fichiers via MQTT ?

Pour des images et fichiers de quelques mégaoctets, le transfert découpé via MQTT fonctionne bien. Pour un firmware ou une vidéo volumineux, publiez une URL de téléchargement via MQTT et laissez l'appareil récupérer le fichier en HTTPS depuis un stockage objet.