Sim, você pode enviar arquivos e imagens via MQTT: um payload é só um conjunto de bytes, então um JPEG ou um arquivo de log pode ser publicado exatamente como está no disco. O trabalho está nos detalhes: ficar abaixo do limite de tamanho do broker, dividir arquivos maiores em chunks, juntá-los de novo na ordem certa e provar que o resultado é idêntico ao original.
Este tutorial mostra quando enviar binário puro e quando base64 faz sentido, um protocolo simples de chunks com manifesto, e um emissor e um receptor em Python que você pode testar em poucos minutos.
Binário ou base64?
O MQTT não se importa com o que está dentro do payload. Não é preciso codificar uma imagem antes de publicá-la; todas as bibliotecas cliente populares aceitam bytes brutos (bytes em Python, Buffer / Uint8Array no MQTT.js, byte[] em Java e .NET).
Base64 é uma escolha que você faz apenas quando os bytes precisam ficar dentro de um documento de texto:
| Abordagem | Overhead | Use quando |
|---|---|---|
| Payload binário bruto | 0 % | Padrão. O receptor sabe que o topic transporta um arquivo. |
| Base64 dentro de JSON | cerca de 33 % mais as chaves JSON | Um consumidor só aceita texto (algumas ferramentas low-code, pontes de webhook, pipelines de log). |
| Payload binário + propriedades MQTT 5 | poucos bytes | Você quer metadados (nome do arquivo, tipo MIME) sem envolver os bytes. |
Uma foto de 600 KB vira cerca de 800 KB depois de codificada em base64, e o receptor precisa fazer o parse do JSON e decodificar antes de usá-la. Se você controla as duas pontas, envie binário.
Com MQTT 5 você pode anexar metadados a um payload binário em vez de envolvê-lo: defina a propriedade Content Type como image/jpeg e coloque o nome do arquivo em uma user property. Deixe o Payload Format Indicator em 0 (bytes não especificados); defini-lo como 1 diz ao broker e ao receptor que o payload é texto UTF-8, e um JPEG não é.
Conheça seus limites de tamanho
A especificação MQTT permite uma remaining length de até 268.435.455 bytes, cerca de 256 MB por pacote. Na prática ninguém chega perto disso, porque brokers e serviços impõem seu próprio máximo:
| Broker ou serviço | Limite típico |
|---|---|
| Mosquitto | Sem limite por padrão; definido com max_packet_size / message_size_limit |
| EMQX | 1 MB por padrão (mqtt.max_packet_size), configurável |
| HiveMQ | max-packet-size configurável, padrão próximo do máximo do protocolo |
| AWS IoT Core | 128 KB por mensagem |
| Azure IoT Hub | 256 KB por mensagem dispositivo-para-nuvem |
Os limites mudam entre versões e planos, então trate a tabela como ponto de partida e confirme no seu próprio broker. No MQTT 5 o servidor anuncia seu limite na propriedade Maximum Packet Size do CONNACK; um publish que o ultrapasse normalmente termina com uma desconexão com reason code 0x95 (Packet too large). Brokers MQTT 3.1.1 geralmente só fecham a conexão, o que parece uma desconexão aleatória. O guia de packet too large explica esses sintomas, e a calculadora de tamanho de pacote mostra quanto o topic e as propriedades somam ao tamanho do payload.
Mensagens grandes também prejudicam de formas menos óbvias: bloqueiam a conexão TCP enquanto estão em trânsito (pings de keep-alive e outras mensagens esperam atrás delas), o broker precisa mantê-las em memória para cada sessão offline que as tenha na fila e, em um link com perdas, um único publish de 5 MB que falha é retransmitido por inteiro.
Um protocolo simples de chunks
Para qualquer coisa maior do que uma mensagem única confortável (uma boa regra é 64–256 KB), divida o arquivo em chunks. Mantenha o protocolo sem surpresas:
- Manifesto em
testmqtt/files/<device>/<file_id>/meta: JSON com nome do arquivo, tamanho total, tamanho do chunk, quantidade de chunks e um hash SHA-256. - Chunks em
testmqtt/files/<device>/<file_id>/chunk/<index>: bytes brutos, sem codificação. - O receptor reúne os chunks pelo índice e, quando tem todos, junta-os e compara o hash.
Por que o índice no topic? O MQTT só garante ordem por topic a partir de um mesmo publicador, então chunks em topics diferentes podem ser entregues em qualquer ordem. Com QoS 1 um chunk também pode chegar duas vezes; guardar os chunks em um mapa indexado torna duplicatas inofensivas. Colocar o índice no topic mantém o payload como dados puros do arquivo; você também poderia prefixar cada chunk com um cabeçalho binário de 4 bytes se preferir um único topic por arquivo.
Use QoS 1 para os chunks. QoS 0 vai acabar perdendo algum em uma rede ruim, e um arquivo com um chunk faltando é inútil. QoS 2 adiciona uma ida e volta por chunk sem muito benefício, porque o receptor já elimina duplicatas.
O emissor (Python)
Instale o cliente com pip install "paho-mqtt>=2". O emissor espera cada PUBACK antes de enviar o próximo chunk, o que dá um controle de fluxo natural e evita estourar a janela inflight do 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 KB por mensagem
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) # espera o PUBACK
time.sleep(0.02) # bem abaixo dos limites compartilhados
print(f"enviado {manifest['name']} em {total} chunks, id {file_id}")
client.loop_stop()
client.disconnect()O receptor (Python)
O receptor assina todos os arquivos do dispositivo, guarda os chunks conforme chegam e grava o arquivo quando o hash confere:
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 # duplicatas apenas sobrescrevem
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"ERRO {meta['name']}: hash não confere")
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 podem chegar antes do manifesto, por isso a verificação de completude roda após cada mensagem, e não só depois do manifesto. Em produção, adicione um timeout que descarte transferências incompletas após um ou dois minutos; caso contrário, um manifesto perdido vaza memória.
Testando a transferência
Rode o receptor em um terminal e o emissor em outro:
python receive_files.py
# segundo terminal
python send_file.py photo.jpg
sha256sum photo.jpg received-photo.jpg # macOS: shasum -a 256Os dois hashes precisam ser iguais. Depois dificulte: envie um arquivo cujo tamanho seja múltiplo exato do tamanho do chunk (o clássico off-by-one), envie um arquivo vazio e derrube a rede no meio da transferência para confirmar que o receptor reporta um arquivo incompleto em vez de gravar lixo. Durante um teste, assine testmqtt/files/demo/+/meta no cliente MQTT online para ver os manifestos chegando.
Para arquivos pequenos de uma única mensagem, os clientes de linha de comando do Mosquitto bastam. -f publica um arquivo como payload, e -N impede que o mosquitto_sub acrescente uma quebra de linha que corromperia o binário:
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.jpgHá mais padrões de linha de comando nos exemplos de mosquitto_pub e mosquitto_sub. Para descobrir a partir de que tamanho seu broker rejeita mensagens, siga o teste de payloads grandes.
Exibindo uma imagem no navegador
Um dashboard pode exibir um snapshot de câmera direto do MQTT sobre WebSockets seguros com MQTT.js 5. O payload chega como bytes, então envolva-o em um 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" }));
});Mantenha pequenas as imagens para o navegador (dezenas de KB): cada aba inscrita baixa cada quadro.
Quando não usar MQTT para arquivos
MQTT é um protocolo de mensageria, não um servidor de arquivos. Para imagens de firmware, clipes de vídeo ou qualquer coisa acima de alguns megabytes, o melhor padrão é enviar o arquivo para um object storage e publicar apenas uma mensagem pequena com a URL, o tamanho e o hash. O dispositivo baixa por HTTPS, que suporta retomada e cache em CDN, e o MQTT faz o que faz de melhor: avisar o dispositivo de que há algo para buscar. As atualizações OTA de firmware via MQTT usam exatamente essa divisão, e as boas práticas de design de payload mostram como manter a mensagem de notificação enxuta.
Acompanhe as transferências no cliente online
Enviar arquivos via MQTT se resume a quatro regras: envie bytes brutos, fique abaixo do limite do broker, numere seus chunks e verifique um hash no final. Construa primeiro o teste de ida e volta, e os casos extremos (duplicatas, chunks fora de ordem, um manifesto perdido) ficam fáceis de ver e corrigir.
Perguntas frequentes
O MQTT consegue enviar arquivos binários?
Sim. Um payload MQTT é uma sequência arbitrária de bytes, então você pode publicar um JPEG, um PDF ou uma imagem de firmware do jeito que estão. Base64 só é necessário quando o payload precisa viajar dentro de um formato de texto como JSON.
Qual é o tamanho máximo de arquivo no MQTT?
O protocolo permite pacotes de cerca de 256 MB, mas brokers e serviços em nuvem costumam impor limites bem menores, muitas vezes entre 128 KB e alguns MB. Verifique o tamanho máximo de pacote do seu broker e divida em chunks tudo que for maior.
Devo enviar arquivos grandes via MQTT?
Para imagens e arquivos de até alguns megabytes, a transferência em chunks via MQTT funciona bem. Para firmware ou vídeo grandes, publique uma URL de download via MQTT e deixe o dispositivo baixar o arquivo por HTTPS a partir de um object storage.

