O código de reconexão é a parte de um cliente MQTT que menos roda em desenvolvimento e mais roda em produção. Para testar a lógica de reconexão MQTT, quebre a conexão de propósito das mesmas formas que o mundo real faz — reinício do broker, conexões recusadas, uma queda silenciosa de rede — e verifique quatro coisas: o cliente percebe, espera com atrasos crescentes, se inscreve de novo e nenhuma mensagem QoS 1 se perde enquanto ele esteve fora.
Este guia traz um cliente de teste em Python que registra tudo, três cenários de falha que você reproduz em minutos e um checklist de verificações.
Como é um comportamento de reconexão "correto"
| Comportamento | O que verificar |
|---|---|
| Detecção | Conexões recusadas/resetadas são percebidas na hora; quedas silenciosas em ~1–2 intervalos de keep-alive |
| Backoff | Os atrasos crescem (por exemplo 1, 2, 4, 8 … s) até um teto e são zerados após uma conexão bem-sucedida |
| Jitter | Os dispositivos não tentam todos ao mesmo tempo — senão você provoca uma tempestade de reconexões |
| Reinscrição | As inscrições são restauradas, quer o broker tenha mantido a sessão ou não |
| Retomada de sessão | Com sessão persistente, as mensagens QoS 1/2 enfileiradas chegam após a reconexão |
| Desistir do jeito certo | Falhas de autenticação são registradas com clareza em vez de martelar o broker para sempre |
A parte de design — como escolher atrasos e configurações de sessão — está em boas práticas de reconexão MQTT. Aqui o foco é provar que o seu cliente faz isso.
Como as bibliotecas mais comuns reconectam
- paho-mqtt (Python 2.x) reconecta automaticamente dentro de
loop_forever()eloop_start().reconnect_delay_set(min_delay, max_delay)controla o backoff exponencial; não há jitter. - MQTT.js 5 reconecta automaticamente a cada
reconnectPeriodmilissegundos (1000 por padrão) — um intervalo fixo, não exponencial. Por padrão, ele se reinscreve nos topics anteriores (resubscribe: true). - Eclipse Paho Java reconecta quando
setAutomaticReconnect(true)está ativado, dobrando a espera até um máximo configurável. - MQTTnet deixa a reconexão para o seu código: você trata o evento de desconexão e chama connect de novo com o seu próprio atraso.
Seja qual for a biblioteca, os padrões raramente são o que você quer em produção — e é justamente por isso que merecem um teste.
Um cliente de teste que registra cada transição
Este cliente paho-mqtt 2.x usa MQTT 5 com sessão persistente, se reinscreve a cada conexão e imprime um timestamp monotônico para cada evento, para você ler o backoff direto na saída:
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) # sempre se reinscrever
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 # manter a sessão por 1 hora
client.connect_async(HOST, PORT, keepalive=10, clean_start=False, properties=props)
client.loop_forever(retry_first_connection=True)Mantenha o client ID fixo durante os testes — uma sessão persistente pertence a um client ID, e dois processos com o mesmo ID vão se derrubar mutuamente sem parar. Um keepalive=10 curto faz as quedas silenciosas aparecerem rápido; use o valor de produção na execução final.
Cenário 1: reinício e queda do broker
Rode um Mosquitto descartável localmente para poder pará-lo quando quiser:
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 2Saída esperada, com os atrasos dobrando até o broker voltar:
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}Os textos exatos dos reason codes variam conforme a versão, mas os intervalos entre tentativas devem ser de 1, 2, 4, 8, 16 e 32 segundos. Repare em session_present: False após o reinício: este Mosquitto não tem persistência, então a sessão morreu junto com o processo. Seu cliente precisa se reinscrever — se as mensagens pararem de chegar depois deste teste, esse é o bug. Ative persistence true no Mosquitto (veja persistência no Mosquitto) e repita para ver a sessão sobreviver a um reinício.
Cenário 2: queda silenciosa de rede
Um reinício do broker fecha os sockets de forma limpa. Um access point Wi-Fi travado ou um timeout de NAT, não — os pacotes simplesmente somem, e o cliente só descobre pelo keep-alive. Em uma máquina Linux, dá para simular com uma regra de firewall:
sudo iptables -I OUTPUT -p tcp --dport 1883 -j DROP
sleep 45
sudo iptables -D OUTPUT -p tcp --dport 1883 -j DROPUse -j REJECT --reject-with tcp-reset em vez de -j DROP para simular um reset imediato. A diferença importa: com DROP, a sonda deve registrar disconnected cerca de 10–20 segundos depois de a regra ser adicionada (um a dois intervalos de keep-alive) e depois aplicar backoff até a regra ser removida. Se ela nunca registrar uma desconexão, o keep-alive não está funcionando — veja keep alive no MQTT.
Para latência, perda de pacotes, limites de banda ou ambientes multiplataforma, um proxy é mais flexível que o iptables; testar com Toxiproxy mostra como injetar essas falhas entre cliente e broker.
Cenário 3: retomada de sessão com mensagens enfileiradas
Este é o teste que a maioria das equipes pula e de onde vem a maioria dos incidentes. Aponte a sonda para um broker que mantém sessões — por exemplo o endpoint compartilhado broker.connectmq.com — e:
- Inicie
reconnect_probe.pye espere oconnectedcom a inscrição ativa. - Bloqueie a porta 1883 com a regra de iptables do cenário 2.
- Com a sonda offline, publique cinco mensagens QoS 1 em
testmqtt/reconnect/probe/qa partir de um caminho que a regra não bloqueia — por exemplo o cliente MQTT online, que conecta por WebSockets seguros na porta 8084. - Remova a regra e observe a sonda reconectar.
A sonda deve registrar session_present: True e, em seguida, as cinco mensagens. Se ela reconectar com session_present: False, o broker descartou a sessão: verifique clean_start, o intervalo de expiração da sessão e se o seu client ID mudou. Se as mensagens chegarem com dup: True ou duas vezes, é o comportamento at-least-once do QoS 1 funcionando como projetado — seu handler precisa ser idempotente. Sessões persistentes explica o que o broker guarda e por quanto tempo.
Outros casos de falha que merecem teste
- Credenciais alteradas enquanto estava offline. O broker responde à reconexão com "Not authorized" ou "Bad user name or password". O cliente deve registrar isso com destaque e desacelerar, não tentar a cada segundo para sempre. Consulte os códigos na ferramenta de reason codes.
- Falha de DNS. Desconecte a rede por completo; erros de resolução devem passar pelo mesmo caminho de backoff que conexões recusadas.
- Client ID duplicado. Inicie a sonda duas vezes. Cada instância derruba a outra e as duas reconectam sem fim — um loop clássico de produção.
- Quedas longas. Deixe o broker parado por mais tempo que
max_delaye verifique se as tentativas se estabilizam no teto em vez de continuar crescendo ou parar. - Reconexão durante publicação. Publique mensagens QoS 1 em loop enquanto corta a rede e conte o que chega a um subscriber após a recuperação.
Checklist de testes de reconexão
- Registre cada conexão, desconexão e tentativa falha com um timestamp monotônico.
- Reinicie o broker: verifique a detecção, os atrasos crescentes e a recuperação.
- Descarte tráfego silenciosamente: verifique a detecção por keep-alive em um a dois intervalos.
- Enfileire mensagens QoS 1 enquanto offline: verifique
session_presente a entrega após reconectar. - Verifique a reinscrição após um reinício sem persistência.
- Teste credenciais erradas, falha de DNS e client IDs duplicados.
- Automatize os cenários no CI com um contêiner de broker descartável.
A lógica de reconexão só é tão boa quanto a última vez que você a viu funcionar. Uma sonda que imprime timestamps, um broker que você pode parar e uma regra de firewall bastam para provar backoff, reinscrição e retomada de sessão — e para pegar a regressão antes que um reinício do broker faça isso por você.
Perguntas frequentes
Por que meu cliente MQTT não recebe mensagens depois de reconectar?
Normalmente a sessão não foi retomada: o cliente conectou com sessão limpa (ou o broker perdeu a sessão ao reiniciar) e não se inscreveu de novo. Reinscreva-se sempre no callback de conexão e verifique a flag session present do CONNACK.
Quanto tempo um cliente MQTT leva para perceber uma conexão morta?
Se a rede cai silenciosamente, o cliente só percebe quando o ping de keep-alive fica sem resposta — normalmente em um ou dois intervalos de keep-alive. Uma conexão recusada ou resetada é percebida na hora.
O paho-mqtt usa backoff exponencial?
Sim. Com loop_forever() ou loop_start(), o paho-mqtt reconecta automaticamente e dobra a espera de min_delay até max_delay, definidos com reconnect_delay_set(). Ele não adiciona jitter aleatório, então adicione o seu se muitos dispositivos seguem o mesmo cronograma.


