Le code de reconnexion est la partie d'un client MQTT qui s'exécute le moins en développement et le plus en production. Pour tester la logique de reconnexion MQTT, cassez volontairement la connexion de la même manière que le monde réel — redémarrage du broker, connexions refusées, coupure réseau silencieuse — et vérifiez quatre points : le client s'en rend compte, il attend avec des délais croissants, il se réabonne, et aucun message QoS 1 n'est perdu pendant son absence.
Ce guide vous fournit un client de test Python qui journalise tout, trois scénarios de panne reproductibles en quelques minutes et une checklist de vérifications.
À quoi ressemble une reconnexion « correcte »
| Comportement | À vérifier |
|---|---|
| Détection | Connexions refusées/réinitialisées détectées aussitôt ; coupures silencieuses en ~1–2 intervalles de keep-alive |
| Backoff | Les délais augmentent (par exemple 1, 2, 4, 8 … s) jusqu'à un plafond, puis repartent à zéro après une connexion réussie |
| Jitter | Les appareils ne réessaient pas en cadence — sinon vous provoquez une tempête de reconnexions |
| Réabonnement | Les abonnements sont rétablis, que le broker ait conservé la session ou non |
| Reprise de session | Avec une session persistante, les messages QoS 1/2 en attente arrivent après la reconnexion |
| Abandonner proprement | Les échecs d'authentification sont journalisés clairement au lieu de marteler le broker indéfiniment |
L'aspect conception — comment choisir les délais et les paramètres de session — est traité dans les bonnes pratiques de reconnexion MQTT. Ici, il s'agit de prouver que votre client le fait vraiment.
Comment les bibliothèques courantes se reconnectent
- paho-mqtt (Python 2.x) se reconnecte automatiquement dans
loop_forever()etloop_start().reconnect_delay_set(min_delay, max_delay)règle le backoff exponentiel ; il n'y a pas de jitter. - MQTT.js 5 se reconnecte automatiquement toutes les
reconnectPeriodmillisecondes (1000 par défaut) — un intervalle fixe, pas exponentiel. Par défaut, il se réabonne aux topics précédents (resubscribe: true). - Eclipse Paho Java se reconnecte si
setAutomaticReconnect(true)est activé, en doublant l'attente jusqu'à un maximum configurable. - MQTTnet laisse la reconnexion à votre code : vous gérez l'événement de déconnexion et rappelez connect avec votre propre délai.
Quelle que soit la bibliothèque, les valeurs par défaut correspondent rarement à ce qu'il faut en production — c'est justement pour cela qu'elles méritent un test.
Un client de test qui journalise chaque transition
Ce client paho-mqtt 2.x utilise MQTT 5 avec une session persistante, se réabonne à chaque connexion et affiche un horodatage monotone pour chaque événement, si bien que vous lisez le backoff directement dans la sortie :
import os, time
import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties
HOST = os.getenv("MQTT_HOST", "localhost")
PORT = int(os.getenv("MQTT_PORT", "1883"))
TOPIC = "testmqtt/reconnect/probe/#"
T0 = time.monotonic()
def log(event, **info):
print(f"{time.monotonic() - T0:8.2f}s {event:<15} {info}", flush=True)
def on_connect(client, userdata, flags, reason_code, properties):
log("connected", rc=str(reason_code), session_present=flags.session_present)
if not reason_code.is_failure:
client.subscribe(TOPIC, qos=1) # toujours se réabonner
def on_disconnect(client, userdata, flags, reason_code, properties):
log("disconnected", rc=str(reason_code))
def on_connect_fail(client, userdata):
log("connect_failed")
def on_message(client, userdata, msg):
log("message", topic=msg.topic, payload=msg.payload.decode(), dup=msg.dup)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id="reconnect-probe-1", protocol=mqtt.MQTTv5)
if os.getenv("MQTT_USER"):
client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
client.on_connect, client.on_disconnect = on_connect, on_disconnect
client.on_connect_fail, client.on_message = on_connect_fail, on_message
client.reconnect_delay_set(min_delay=1, max_delay=32)
props = Properties(PacketTypes.CONNECT)
props.SessionExpiryInterval = 3600 # conserver la session 1 heure
client.connect_async(HOST, PORT, keepalive=10, clean_start=False, properties=props)
client.loop_forever(retry_first_connection=True)Gardez un client ID fixe pendant les tests : une session persistante appartient à un client ID, et deux processus avec le même ID vont s'éjecter mutuellement en boucle. Un keepalive=10 court fait apparaître rapidement les coupures silencieuses ; utilisez votre valeur de production pour l'essai final.
Scénario 1 : redémarrage et panne du broker
Lancez localement un Mosquitto jetable que vous pourrez arrêter à volonté :
docker run -d --name mqtt-reconnect -p 1883:1883 eclipse-mosquitto:2 \
mosquitto -c /mosquitto-no-auth.conf
python reconnect_probe.py # terminal 1
docker stop mqtt-reconnect && sleep 60 && docker start mqtt-reconnect # terminal 2Sortie attendue, avec des délais qui doublent jusqu'au retour du broker :
0.03s connected {'rc': 'Success', 'session_present': False}
12.40s disconnected {'rc': 'Unspecified error'}
13.41s connect_failed {}
15.42s connect_failed {}
19.43s connect_failed {}
27.44s connect_failed {}
43.45s connect_failed {}
75.46s connected {'rc': 'Success', 'session_present': False}Le libellé exact des reason codes varie selon la version, mais les écarts entre tentatives doivent être de 1, 2, 4, 8, 16 et 32 secondes. Notez session_present: False après le redémarrage : ce Mosquitto n'a pas de persistance, la session a donc disparu avec le processus. Votre client doit se réabonner — si les messages cessent d'arriver après ce test, c'est là qu'est le bug. Activez persistence true dans Mosquitto (voir la persistance Mosquitto) et recommencez pour voir la session survivre à un redémarrage.
Scénario 2 : coupure réseau silencieuse
Un redémarrage du broker ferme proprement les sockets. Un point d'accès Wi-Fi bloqué ou un timeout NAT, non : les paquets disparaissent, et le client ne l'apprend que par le keep-alive. Sur une machine Linux, une règle de pare-feu suffit à le simuler :
sudo iptables -I OUTPUT -p tcp --dport 1883 -j DROP
sleep 45
sudo iptables -D OUTPUT -p tcp --dport 1883 -j DROPUtilisez -j REJECT --reject-with tcp-reset au lieu de -j DROP pour simuler une réinitialisation immédiate. La différence compte : avec DROP, la sonde doit journaliser disconnected environ 10 à 20 secondes après l'ajout de la règle (un à deux intervalles de keep-alive), puis appliquer le backoff jusqu'au retrait de la règle. Si elle ne journalise jamais de déconnexion, le keep-alive ne fonctionne pas — voir le keep alive MQTT.
Pour la latence, la perte de paquets, les limites de bande passante ou les environnements multiplateformes, un proxy est plus souple qu'iptables ; tester avec Toxiproxy montre comment injecter ces pannes entre client et broker.
Scénario 3 : reprise de session avec messages en attente
C'est le test que la plupart des équipes sautent, et d'où viennent la plupart des incidents. Pointez la sonde vers un broker qui conserve les sessions — par exemple l'endpoint partagé broker.connectmq.com — puis :
- Lancez
reconnect_probe.pyet attendezconnectedavec l'abonnement en place. - Bloquez le port 1883 avec la règle iptables du scénario 2.
- Pendant que la sonde est hors ligne, publiez cinq messages QoS 1 sur
testmqtt/reconnect/probe/qpar un chemin que la règle ne bloque pas — par exemple le client MQTT en ligne, qui se connecte en WebSockets sécurisés sur le port 8084. - Retirez la règle et regardez la sonde se reconnecter.
La sonde doit journaliser session_present: True, puis les cinq messages. Si elle se reconnecte avec session_present: False, le broker a abandonné la session : vérifiez clean_start, l'intervalle d'expiration de session et si votre client ID a changé. Si des messages arrivent avec dup: True ou en double, c'est le comportement at-least-once du QoS 1 qui fonctionne comme prévu — votre handler doit être idempotent. Les sessions persistantes expliquent ce que le broker conserve et combien de temps.
D'autres cas de panne à tester
- Identifiants modifiés pendant la déconnexion. Le broker répond à la reconnexion par « Not authorized » ou « Bad user name or password ». Le client doit le journaliser clairement et ralentir, pas réessayer chaque seconde indéfiniment. Recherchez les codes avec l'outil reason codes.
- Panne DNS. Coupez complètement le réseau ; les erreurs de résolution doivent suivre le même chemin de backoff que les connexions refusées.
- Client ID en double. Lancez la sonde deux fois. Chaque instance éjecte l'autre et les deux se reconnectent sans fin — une boucle classique en production.
- Pannes longues. Laissez le broker arrêté plus longtemps que
max_delayet vérifiez que les tentatives se stabilisent au plafond au lieu de continuer à croître ou de s'arrêter. - Reconnexion pendant une publication. Publiez des messages QoS 1 en boucle pendant que vous coupez le réseau, puis comptez ce qui arrive chez un subscriber après le rétablissement.
Checklist des tests de reconnexion
- Journalisez chaque connexion, déconnexion et tentative échouée avec un horodatage monotone.
- Redémarrez le broker : vérifiez la détection, les délais croissants et le rétablissement.
- Supprimez le trafic silencieusement : vérifiez la détection par keep-alive en un à deux intervalles.
- Mettez des messages QoS 1 en attente pendant la déconnexion : vérifiez
session_presentet la livraison après reconnexion. - Vérifiez le réabonnement après un redémarrage sans persistance.
- Testez des identifiants erronés, une panne DNS et des client IDs en double.
- Automatisez les scénarios en CI avec un conteneur de broker jetable.
Une logique de reconnexion ne vaut que par la dernière fois où vous l'avez vue fonctionner. Une sonde qui affiche des horodatages, un broker que vous pouvez arrêter et une règle de pare-feu suffisent à prouver le backoff, le réabonnement et la reprise de session — et à attraper la régression avant qu'un redémarrage du broker ne s'en charge à votre place.
Questions fréquentes
Pourquoi mon client MQTT ne reçoit-il plus de messages après la reconnexion ?
Le plus souvent, la session n'a pas été reprise : le client s'est connecté avec une session propre (ou le broker a perdu la session au redémarrage) et ne s'est pas réabonné. Réabonnez-vous à chaque connexion dans le callback et vérifiez le flag session present du CONNACK.
Combien de temps faut-il à un client MQTT pour détecter une connexion morte ?
Si le réseau tombe silencieusement, le client ne s'en aperçoit que lorsque son ping de keep-alive reste sans réponse, généralement en un à deux intervalles de keep-alive. Une connexion refusée ou réinitialisée est détectée immédiatement.
paho-mqtt utilise-t-il un backoff exponentiel ?
Oui. Avec loop_forever() ou loop_start(), paho-mqtt se reconnecte automatiquement et double l'attente de min_delay à max_delay, réglés avec reconnect_delay_set(). Il n'ajoute aucun jitter aléatoire : ajoutez le vôtre si de nombreux appareils suivent le même rythme.


