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 :
| Approche | Surcoût | À utiliser quand |
|---|---|---|
| Payload binaire brut | 0 % | Par défaut. Le récepteur sait que le topic transporte un fichier. |
| Base64 dans du JSON | environ 33 % plus les clés JSON | Un consommateur n'accepte que du texte (certains outils low-code, passerelles webhook, pipelines de logs). |
| Payload binaire + propriétés MQTT 5 | quelques octets | Vous 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 service | Limite typique |
|---|---|
| Mosquitto | Aucune limite par défaut ; réglable avec max_packet_size / message_size_limit |
| EMQX | 1 Mo par défaut (mqtt.max_packet_size), configurable |
| HiveMQ | max-packet-size configurable, valeur par défaut proche du maximum du protocole |
| AWS IoT Core | 128 Ko par message |
| Azure IoT Hub | 256 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 :
- 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. - Chunks sur
testmqtt/files/<device>/<file_id>/chunk/<index>: octets bruts, sans encodage. - 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 :
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 :
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 :
python receive_files.py
# second terminal
python send_file.py photo.jpg
sha256sum photo.jpg received-photo.jpg # macOS : shasum -a 256Les 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 :
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.jpgVous 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 :
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.

