Fundamentos de MQTT

Estructura de los paquetes MQTT: cabecera fija, cabecera variable y payload

Un recorrido a nivel de byte por los paquetes MQTT: cómo codifica la cabecera fija el tipo y los flags, cómo funciona la longitud restante y qué contienen la cabecera variable y el payload, usando un CONNECT y un PUBLISH que puedes capturar con Wireshark.

Volcado hexadecimal de un PUBLISH de MQTT dividido en cabecera fija, cabecera variable y payload para explicar la estructura de los paquetes MQTT
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

La estructura de los paquetes MQTT es compacta a propósito: cada paquete empieza con una cabecera fija de apenas dos bytes, seguida de una cabecera variable opcional y un payload opcional. Este recorrido decodifica byte a byte paquetes CONNECT, CONNACK y PUBLISH reales, para que puedas leer con soltura una captura de Wireshark o un volcado hexadecimal y detectar errores de codificación en tus propios clientes.

Las tres partes de todo paquete MQTT

Todos los paquetes de control MQTT siguen la misma disposición:

Parte Presente en Contenido
Cabecera fija Todos los paquetes Tipo de paquete, flags, longitud restante
Cabecera variable La mayoría de los paquetes Campos propios del paquete, como el nombre del topic, el identificador de paquete, el nombre del protocolo y, en MQTT 5, las propiedades
Payload Algunos paquetes Client ID y credenciales en CONNECT, mensaje de la aplicación en PUBLISH, filtros de topic en SUBSCRIBE

Los paquetes más pequeños, PINGREQ, PINGRESP y un DISCONNECT de MQTT 3.1.1, son solo la cabecera fija de dos bytes. Esa economía explica en gran parte por qué MQTT funciona bien en enlaces lentos, y es la razón por la que entender la estructura ayuda a estimar el consumo de ancho de banda MQTT.

La cabecera fija

El primer byte se divide en dos mitades:

Bit:     7  6  5  4 | 3  2  1  0
         packet type | flags

Los cuatro bits altos contienen el tipo de paquete, de 1 (CONNECT) a 15 (AUTH). Los cuatro bits bajos son flags. En la mayoría de los paquetes, los flags son valores fijos y reservados; solo PUBLISH les da un uso real:

Bit Significado en PUBLISH
3 DUP: es una reentrega de un mensaje QoS 1 o 2
2-1 Nivel de QoS: 00, 01 o 10
0 RETAIN: el broker debe almacenar este mensaje

Así, un primer byte 0x30 es un PUBLISH QoS 0, 0x32 es QoS 1, 0x31 es un mensaje QoS 0 retenido y 0x3A es una reentrega QoS 1 con DUP activado. SUBSCRIBE, UNSUBSCRIBE y PUBREL deben tener los flags 0010, por eso aparecen como 0x82, 0xA2 y 0x62. La referencia de paquetes de control MQTT recoge todos los tipos con su primer byte.

Codificación de la longitud restante

Tras el primer byte viene la longitud restante (remaining length): el número de bytes de la cabecera variable más el payload, sin contar la propia cabecera fija. Usa un entero de bytes variables, de uno a cuatro bytes. Cada byte transporta siete bits de valor, y el bit superior indica que "sigue otro byte".

Rango de valores Bytes usados
0 a 127 1
128 a 16.383 2
16.384 a 2.097.151 3
2.097.152 a 268.435.455 4

Ejemplo: una longitud restante de 321. Como 321 = 2 x 128 + 65, el primer byte es 65 con el bit de continuación activado (0xC1) y el segundo byte es 2 (0x02). El máximo de cuatro bytes es el origen del límite de 256 MB por paquete que tanto se cita del protocolo; la guía del límite de tamaño de mensaje MQTT explica por qué los brokers reales fijan límites mucho más bajos.

Este es un decodificador que puedes usar al leer volcados hexadecimales:

def decode_remaining_length(data: bytes, offset: int = 1):
    multiplier, value = 1, 0
    for i in range(4):
        byte = data[offset + i]
        value += (byte & 0x7F) * multiplier
        if byte & 0x80 == 0:
            return value, i + 1  # valor, número de bytes usados
        multiplier *= 128
    raise ValueError("Malformed remaining length")

print(decode_remaining_length(bytes([0x30, 0xC1, 0x02])))  # (321, 2)

Recorrido por una sesión real

Ejecuta este comando para publicar un mensaje en el servidor MQTT público gratuito con MQTT 3.1.1, que es lo que mosquitto_pub usa por defecto:

mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-a1b2 \
  -t "connectmq/demo/a1b2/t" -m "21.5" -d

El puerto 1883 no está cifrado, así que una captura de paquetes muestra todos los bytes. En Wireshark, captura en tu interfaz de red y aplica el filtro de visualización mqtt. Wireshark decodifica automáticamente el puerto 1883 como MQTT; verás CONNECT, CONNACK, PUBLISH y DISCONNECT. Usa tu propio segmento de topic aleatorio y recuerda que cualquier cosa en el servidor público puede ser leída por otros: sirve para pruebas y prototipos, no para datos de producción.

CONNECT

El primer paquete del cliente tiene 23 bytes:

10 15                         Cabecera fija: tipo 1 (CONNECT), longitud restante 21
00 04 4D 51 54 54             Nombre del protocolo: longitud 4, "MQTT"
04                            Nivel de protocolo: 4 = MQTT 3.1.1
02                            Flags de conexión: Clean Session
00 3C                         Keep alive: 60 segundos
00 09 64 65 6D 6F 2D 61 31 62 32   Payload: longitud del client ID 9, "demo-a1b2"

El byte de flags de conexión merece una mirada más atenta. El bit 1 es Clean Session, el bit 2 Will Flag, los bits 3-4 Will QoS, el bit 5 Will Retain, el bit 6 Password Flag y el bit 7 User Name Flag. Un cliente que se conecta con usuario, contraseña y clean session enviaría 0xC2. Si esos flags están activados, el usuario y la contraseña siguen al client ID en el payload, cada uno con un prefijo de longitud de dos bytes. Es una de las razones para usar siempre TLS en el puerto 8883 con credenciales reales: en el puerto 1883 viajan en texto plano.

CONNACK

El broker responde con cuatro bytes:

20 02 00 00

Tipo 2, longitud restante 2, Session Present 0, código de retorno 0 (aceptada). Cualquier otro byte final es un rechazo; la guía de códigos de retorno CONNACK de MQTT decodifica cada valor.

PUBLISH

El mensaje en sí tiene 29 bytes:

30 1B                         Cabecera fija: PUBLISH, QoS 0, sin retain; longitud restante 27
00 15                         Longitud del topic: 21
63 6F 6E 6E 65 63 74 6D 71 2F 64 65 6D 6F 2F 61 31 62 32 2F 74
                              Topic: "connectmq/demo/a1b2/t"
32 31 2E 35                   Payload: "21.5"

Fíjate en lo que falta: no hay identificador de paquete, porque los mensajes QoS 0 nunca se confirman. Con -q 1, el primer byte pasa a ser 0x32, aparecen dos bytes de identificador de paquete después del topic y el broker responde con un PUBACK de cuatro bytes (40 02 más el identificador). Tampoco hay un campo de longitud del payload; el payload es simplemente lo que queda tras la cabecera variable, según la longitud restante.

DISCONNECT

Por último, el cliente envía E0 00: tipo 14, longitud restante 0. En MQTT 3.1.1 ese es el paquete completo.

Qué cambia en MQTT 5

MQTT 5 mantiene el mismo esqueleto, pero añade propiedades a la cabecera variable de la mayoría de los paquetes. Una sección de propiedades empieza con su propia longitud en forma de entero de bytes variables, seguida de pares identificador-valor. Aunque no haya propiedades, el byte de longitud 00 está presente.

Para la misma sesión sobre MQTT 5:

  • CONNECT tiene el nivel de protocolo 05 y una longitud de propiedades después del keep alive.
  • CONNACK crece para incluir un reason code y propiedades, que a menudo anuncian límites como Maximum Packet Size, Topic Alias Maximum o Maximum QoS.
  • PUBLISH tiene una longitud de propiedades después del topic (y del identificador de paquete), así que una publicación QoS 0 sin propiedades ocupa un byte más.
  • DISCONNECT puede llevar un reason code y propiedades, que es como el broker explica por qué cerró una conexión.

Propiedades como Topic Alias, Message Expiry Interval y User Properties son las que hacen posibles la mayoría de las funciones de MQTT 5.

Errores de codificación habituales y cómo detectarlos

Si escribes un cliente propio o interpretas MQTT en una pasarela, comprueba primero estos puntos:

  1. Longitud restante escrita como un campo fijo de dos bytes. Funciona con paquetes pequeños y falla por encima de 127 bytes.
  2. Longitudes de cadena contadas en caracteres en lugar de bytes. Los nombres de topic no ASCII tienen más bytes que caracteres, lo que produce paquetes mal formados.
  3. Falta el identificador de paquete en QoS 1 o 2. El broker lee parte de tu payload como identificador y el topic está bien, así que el fallo parece una corrupción de datos.
  4. Flags reservados incorrectos en SUBSCRIBE o PUBREL, que los brokers deben tratar como mal formados, cerrando la conexión.
  5. Olvidar la longitud de propiedades vacía al pasar un cliente a MQTT 5.

Wireshark marca claramente los paquetes mal formados, y un broker MQTT 5 responde con el reason code 0x81 Malformed Packet en el DISCONNECT, lo que es mucho más rápido de diagnosticar que un cierre silencioso. Para las definiciones de referencia, consulta la especificación OASIS MQTT.

Inspecciona paquetes contra un broker real de ConnectMQ

Leer paquetes es la forma más rápida de entender MQTT, y el tutorial de MQTT construye sobre esta base con sesiones, QoS y mensajes retenidos. ConnectMQ mantiene el servidor público en broker.connectmq.com precisamente para este tipo de experimentos, y brokers privados en el puerto TLS 8883 cuando entran en juego datos reales.

Pasa de las capturas de paquetes a producción con ConnectMQ

Cuando tu cliente esté listo, un broker MQTT gestionado te ofrece endpoints cifrados, credenciales emitidas y un portal con cliente en el navegador y explorador de topics en directo. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, o lee más sobre cómo elegir un broker MQTT en la nube.

Preguntas frecuentes

¿Qué tamaño tiene el paquete MQTT más pequeño?

Dos bytes. PINGREQ, PINGRESP y un DISCONNECT de MQTT 3.1.1 constan solo de la cabecera fija: un byte para el tipo y los flags y un byte de longitud restante a cero.

¿Qué incluye la longitud restante?

Cuenta los bytes de la cabecera variable y del payload, pero no los de la propia cabecera fija. Se codifica como un entero de bytes variables de uno a cuatro bytes.

¿Por qué puedo leer mi contraseña en Wireshark?

Porque el puerto 1883 no está cifrado y el CONNECT lleva el usuario y la contraseña en su payload. Usa TLS en el puerto 8883 o WebSockets seguros en el 8084 para cualquier credencial real.

¿Incluye el paquete PUBLISH la longitud del payload?

No. La longitud del payload está implícita: es la longitud restante menos el tamaño de la cabecera variable, que incluye el nombre del topic, el identificador de paquete en QoS 1 y 2 y, en MQTT 5, las propiedades.

Artículos relacionados

¿Quién inventó MQTT? La historia, de los oleoductos al IoT

MQTT lo inventaron en 1999 Andy Stanford-Clark, de IBM, y Arlen Nipper, de Arcom, para monitorizar oleoductos vía satélite. Sigue su camino hacia el open source, OASIS, ISO y los brokers MQTT gestionados en la nube de hoy.

8 min de lectura

Por qué tu cliente MQTT se desconecta constantemente (y cómo solucionarlo)

Los bucles de reconexión son uno de los problemas más habituales de MQTT. Conoce las cinco causas típicas, desde los client ID duplicados hasta los timeouts de keep alive y los paquetes demasiado grandes, y sigue un flujo de diagnóstico que localiza la tuya.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.