Tutoriais

Enviar arquivos e imagens via MQTT: chunks, base64 e remontagem

Como enviar arquivos e imagens via MQTT: binário vs base64, limites de tamanho do broker, divisão em chunks com manifesto e remontagem verificada com SHA-256.

Atualizado · 9 min de leitura

Enviar arquivos e imagens via MQTT: chunks, base64 e remontagem

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:

AbordagemOverheadUse quando
Payload binário bruto0 %Padrão. O receptor sabe que o topic transporta um arquivo.
Base64 dentro de JSONcerca de 33 % mais as chaves JSONUm consumidor só aceita texto (algumas ferramentas low-code, pontes de webhook, pipelines de log).
Payload binário + propriedades MQTT 5poucos bytesVocê 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çoLimite típico
MosquittoSem limite por padrão; definido com max_packet_size / message_size_limit
EMQX1 MB por padrão (mqtt.max_packet_size), configurável
HiveMQmax-packet-size configurável, padrão próximo do máximo do protocolo
AWS IoT Core128 KB por mensagem
Azure IoT Hub256 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:

  1. 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.
  2. Chunks em testmqtt/files/<device>/<file_id>/chunk/<index>: bytes brutos, sem codificação.
  3. 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:

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 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:

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   # 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:

Teste de ida e volta
python receive_files.py
# segundo terminal
python send_file.py photo.jpg
sha256sum photo.jpg received-photo.jpg     # macOS: shasum -a 256

Os 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:

Imagem em uma única mensagem com 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

Há 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:

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" }));
});

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.