Pruebas de brokers

Cómo probar los topic aliases de MQTT 5

Cómo probar los topic aliases de MQTT 5: lee el Topic Alias Maximum, verifica publicaciones con alias, mide el ahorro y reproduce errores Topic Alias invalid.

Actualizado · 8 min de lectura

Cómo probar los topic aliases de MQTT 5

Los topic aliases de MQTT 5 sustituyen un nombre de topic largo por un número de 2 bytes durante el resto de la conexión. El primer PUBLISH lleva el topic y el alias. Cada PUBLISH posterior envía un topic vacío más el alias. Para probarlos, lee el Topic Alias Maximum que concede el broker en su CONNACK, publica con y sin nombre de topic, comprueba que los suscriptores reciben el topic completo y reproduce los dos casos de error: un alias fuera de rango y un alias que el broker no conoce.

El mecanismo en sí se explica en los topic aliases de MQTT. Este artículo es el plan de pruebas.

Cómo se negocian los alias

DirecciónQuién fija el límiteDóndePor defecto
Cliente → brokerBrokerTopic Alias Maximum en el CONNACK0 (sin alias)
Broker → clienteClienteTopic Alias Maximum en el CONNECT0 (sin alias)

Reglas que sigue una implementación correcta:

  • Los valores de alias van de 1 al máximo negociado. 0 nunca es válido.
  • El emisor elige el mapeo. Enviar un topic nuevo con un número de alias existente lo vuelve a mapear.
  • Los mapeos son por conexión y por dirección. Desaparecen al desconectar, incluso con sesión persistente.
  • Un PUBLISH con topic vacío debe llevar un alias ya mapeado en esta conexión.

¿Cuánto ahorra un alias?

Toma un topic de telemetría típico, factory/line-3/press-07/telemetry/vibration (43 caracteres), un payload de 20 bytes y QoS 1:

CampoTopic completoCon alias
Cabecera fija2 bytes2 bytes
Nombre del topic (longitud de 2 bytes + texto)45 bytes2 bytes (vacío)
Identificador de paquete2 bytes2 bytes
Propiedades (longitud + Topic Alias)1 byte4 bytes
Payload20 bytes20 bytes
Total70 bytes30 bytes

Son 40 bytes ahorrados por mensaje, más de la mitad del paquete. A un mensaje por segundo, un dispositivo ahorra unos 3,5 MB al día. El ahorro solo importa cuando los topics son largos en comparación con los payloads y cuando los bytes cuestan dinero o batería, como en enlaces celulares, satelitales o LPWAN. En una LAN con payloads de 2 KB es ruido. La calculadora de tamaño de paquete muestra las cifras para tus propios topics, y el overhead de ancho de banda de MQTT cubre otras formas de reducir el tráfico. TLS añade su propio overhead por registro, así que mide en el cable si el presupuesto es ajustado.

Prueba 1: leer el máximo negociado

Antes de nada, registra lo que concede el broker. Los brokers difieren: algunos permiten muchos alias, otros solo unos pocos y otros los desactivan (máximo 0). Un cliente que usa alias sin comprobarlo será desconectado en brokers que no permiten ninguno.

Con la CLI de Mosquitto, -d imprime el CONNACK, pero la salida de propiedades varía entre versiones. Un script es más fiable. La prueba de abajo imprime primero el máximo y se detiene si es 0.

Prueba 2: las publicaciones con alias llegan intactas

Este script de paho-mqtt 2.x publica un mensaje con el topic completo y el alias 1, otros cuatro con topic vacío y alias 1, y comprueba que un suscriptor normal recibe los cinco con el nombre completo del topic. Después reconecta y envía el alias obsoleto para ver cómo reacciona el broker.

alias_test.py
import time, uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

HOST, PORT = "broker.connectmq.com", 1883
RUN = uuid.uuid4().hex[:8]
TOPIC = f"testmqtt/alias/{RUN}/factory/line-3/press-07/telemetry"
received = []

def client(cid, log):
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{cid}-{RUN}", protocol=mqtt.MQTTv5)
    c.on_connect = lambda cl, u, f, rc, props: log.update(max=getattr(props, "TopicAliasMaximum", 0))
    c.on_disconnect = lambda cl, u, f, rc, props: log.update(disconnect=str(rc))
    return c

def alias_props(alias):
    p = Properties(PacketTypes.PUBLISH)
    p.TopicAlias = alias
    return p

# Subscriber: a normal client that does not accept aliases from the broker
sub_log = {}
sub = client("alias-sub", sub_log)
sub.on_message = lambda cl, u, msg: received.append((msg.topic, msg.payload.decode()))
sub.connect(HOST, PORT)
sub.loop_start()
sub.subscribe(TOPIC, qos=1)

# Test 1: negotiation + valid alias use
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
print("broker Topic Alias Maximum:", log.get("max"))
if not log.get("max"):
    raise SystemExit("broker does not accept topic aliases from clients (maximum 0)")

pub.publish(TOPIC, "m0", qos=1, properties=alias_props(1))      # set alias 1 = TOPIC
for i in range(1, 5):
    pub.publish("", f"m{i}", qos=1, properties=alias_props(1))  # topic omitted, alias only
time.sleep(2)
ok = received == [(TOPIC, f"m{i}") for i in range(5)]
print("PASS" if ok else "FAIL", "aliased messages arrive with the full topic:", len(received), "of 5")
pub.disconnect(); pub.loop_stop()

# Test 2: alias mappings do not survive a reconnect
received.clear()
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
pub.publish("", "after-reconnect", qos=1, properties=alias_props(1))  # unknown on this connection
time.sleep(2)
print("PASS" if not received else "FAIL", "stale alias after reconnect is not delivered;",
      "broker reaction:", log.get("disconnect", "no DISCONNECT, message dropped"))
pub.disconnect(); pub.loop_stop()

# Test 3: alias above the negotiated maximum (only possible if the maximum is below 65535)
if log.get("max", 0) < 65535:
    log2 = {}
    bad = client("alias-bad", log2)
    bad.connect(HOST, PORT)
    bad.loop_start()
    time.sleep(1)
    bad.publish(TOPIC, "too-high", qos=1, properties=alias_props(log2["max"] + 1))
    time.sleep(2)
    print("broker reaction to alias > maximum:", log2.get("disconnect", "none"))
    bad.loop_stop()

sub.disconnect(); sub.loop_stop()

Una ejecución típica imprime:

Salida
broker Topic Alias Maximum: 65535
PASS aliased messages arrive with the full topic: 5 of 5
PASS stale alias after reconnect is not delivered; broker reaction: no DISCONNECT, message dropped

paho-mqtt no gestiona los alias por ti. Envía exactamente el alias que fijas y no resuelve los alias que llegan del broker, así que nunca anuncies un Topic Alias Maximum en un suscriptor paho. Eso también lo convierte en una buena herramienta para pruebas negativas. paho sí rechaza localmente el alias 0 (TopicAlias property value must be in the range 1-65535), así que para probar cómo maneja el broker el alias 0 necesitas una herramienta de paquetes en bruto.

Alias automáticos con MQTT.js

MQTT.js 5 puede asignar los alias por ti. Con autoAssignTopicAlias mapea topics a alias según el uso menos reciente (LRU), dentro del máximo del broker. Registra los paquetes para verificar qué sale por el cable:

alias.mjs
import mqtt from "mqtt";

const topic = "testmqtt/alias/js-demo/factory/line-3/press-07/telemetry";
const client = mqtt.connect("mqtt://broker.connectmq.com:1883", {
  protocolVersion: 5,
  clientId: `alias-js-${Math.random().toString(16).slice(2, 10)}`,
  autoAssignTopicAlias: true,             // let MQTT.js pick and reuse aliases
  properties: { topicAliasMaximum: 10 },  // accept up to 10 aliases from the broker
});

// Log what actually goes over the wire
client.on("packetsend", (p) => {
  if (p.cmd === "publish") console.log(`sent topic="${p.topic}" alias=${p.properties?.topicAlias}`);
});

client.on("connect", (connack) => {
  console.log("broker Topic Alias Maximum:", connack.properties?.topicAliasMaximum ?? 0);
  let n = 0;
  const timer = setInterval(() => {
    client.publish(topic, JSON.stringify({ n, vibration: Math.random() }), { qos: 1 });
    if (++n === 5) {
      clearInterval(timer);
      setTimeout(() => client.end(), 500);
    }
  }, 300);
});

client.on("error", (err) => console.error("error:", err.message));

Salida esperada: la primera línea muestra el topic completo con alias=1 y las cuatro siguientes topic="" con alias=1. Si todas las líneas muestran el topic completo, el broker concedió un máximo de 0 y MQTT.js volvió en silencio a publicaciones normales. Es el comportamiento correcto, pero significa que no estás ahorrando nada. autoUseTopicAlias es la opción más conservadora: solo reutiliza alias que registraste tú.

Prueba 3: los casos de error

Qué envíasQué dice la especificaciónError que la gente busca
Alias 0Error de protocoloDISCONNECT 0x94 Topic Alias invalid
Alias por encima del máximo negociadoError de protocoloDISCONNECT 0x94 Topic Alias invalid
Topic vacío, alias nunca mapeadoError de protocoloDISCONNECT 0x82 Protocol Error
Cualquier alias con máximo 0Error de protocolo0x94, o en el cliente Sending Topic Alias out of range en MQTT.js
Alias obsoleto tras reconectarIgual que "nunca mapeado"0x82, o el mensaje desaparece en silencio

No todos los brokers reaccionan igual. Algunos envían el DISCONNECT con el reason code, otros cierran la conexión o descartan ese mensaje sin avisar. La ejecución de arriba muestra un broker que descartó el mensaje con alias obsoleto sin desconectar. Por eso tu prueba debe comprobar el resultado importante, el mensaje erróneo no se entregó con un topic equivocado, y registrar la reacción del broker como información. Descifra cualquier reason code con la consulta de reason codes. Las soluciones para cada caso están en errores Topic Alias invalid y el error de protocolo MQTT 0x82.

Verificar el lado del suscriptor

Los suscriptores nunca deberían notar los alias, salvo que lo hayan pedido. Abre el cliente MQTT online, suscríbete a testmqtt/alias/# y ejecuta cualquiera de los dos scripts: cada mensaje debe mostrar el topic completo. Si un suscriptor que no anunció un Topic Alias Maximum recibe alguna vez un topic vacío, el broker tiene un fallo. Es un bug raro pero grave que merece la pena reportar.

Ver mensajes con alias llegar con el topic completo

Checklist

  1. Registra el Topic Alias Maximum de cada CONNACK y nunca lo superes.
  2. Envía primero el topic completo con el alias, luego publicaciones solo con alias, y comprueba que los suscriptores ven topics completos.
  3. Reconecta y confirma que no se usan alias obsoletos.
  4. Prueba un alias por encima del máximo y un alias sin mapear, y registra la reacción del broker.
  5. Mide el ahorro real de bytes para tus topics antes de convertir los alias en un requisito.

Los topic aliases son una mejora barata en enlaces limitados e inútiles en el resto. Prueba la negociación y el caso de reconexión, y ahorrarán ancho de banda sin convertirse en la razón por la que un dispositivo se queda en silencio.

Preguntas frecuentes

¿Cuál es el valor por defecto de Topic Alias Maximum?

0. Si el CONNECT o el CONNACK no incluyen la propiedad Topic Alias Maximum, el receptor no acepta ningún alias. Cada dirección se negocia por separado: el valor del cliente limita al broker y el valor del broker limita al cliente.

¿Los topic aliases sobreviven a una reconexión?

No. Los mapeos de alias solo existen durante una conexión de red, aunque la sesión sea persistente. Tras cada reconexión, el cliente debe volver a enviar el nombre completo del topic antes de poder usar el alias.

¿Los suscriptores ven el alias?

Solo si lo pidieron. Los alias son por salto: el broker resuelve el alias del publicador al topic completo y solo usa alias hacia un suscriptor que anunció su propio Topic Alias Maximum. La mayoría de suscriptores simplemente recibe el nombre completo del topic.