MQTT: the IoT broker protocol

Tiny devices don't talk to each other directly — they publish to topics, and a broker fans each message out to whoever subscribed.

The idea

A temperature sensor doesn't know your phone exists, and your phone doesn't know the sensor exists. Instead, the sensor publishes a reading to a named topic like home/kitchen/temp, and anyone who subscribed to a matching topic filter receives a copy. A central broker sits in the middle and does the matching and the fan-out.

This decouples producers from consumers completely: they never need each other's address, and you can add a tenth subscriber without touching the sensor. That fit — small message headers, one persistent TCP connection, a server doing the routing — is exactly what makes MQTT suit constrained IoT devices.

Publish:
QoS:
QoS 0 — the broker delivers and never confirms. If the network drops it, it’s gone.
Pick a publisher, set the QoS, then press Play to watch the broker route the message.

How it works

Each client opens one long-lived TCP connection to the broker. A subscriber registers a topic filter (which may contain wildcards); a publisher sends a message to a concrete topic name (no wildcards allowed). The broker matches filters against the topic and forwards a copy to each match. Here is a minimal client with paho-mqtt in Python.

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    # '+' = exactly one level, '#' = the rest of the tree (must be last)
    client.subscribe("home/+/temp", qos=1)

def on_message(client, userdata, msg):
    print(msg.topic, msg.payload.decode())   # e.g. home/kitchen/temp 21.4

client = mqtt.Client(client_id="thermostat-1", clean_session=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.local", 1883, keepalive=60)

# Publishers send to a concrete topic, never a wildcard:
client.publish("home/kitchen/temp", payload="21.4", qos=1, retain=False)

client.loop_forever()

The broker never inspects the payload — to it the message is opaque bytes. All the routing happens on the topic string alone.

Cost

LevelGuaranteeRound tripsDuplicates?
QoS 0 · at most onceFire and forget — may be lostPUBLISH onlyNo (or lost)
QoS 1 · at least onceDelivered, retried until ackedPUBLISHPUBACKPossible
QoS 2 · exactly onceDelivered once, no dupes4-step: PUBLISHPUBRECPUBRELPUBCOMPNo

Higher QoS costs more round trips, latency, and broker state. The broker is the single point of fan-out, so every message flows through it. A retained message is one the broker stores per topic and hands to any future subscriber the moment it subscribes — convenient for a "last known value", but it means a brand-new subscriber can receive a message that was published long before it connected.

Watch out for

Worked example

A thermostat subscribes with the filter home/+/temp — "any room's temperature". The kitchen sensor then publishes 21.4 to the concrete topic home/kitchen/temp at QoS 1.

The broker walks the topic level by level against each subscriber's filter. For the thermostat: home=home matches, + swallows kitchen, temp=temp matches — hit. It also matches the phone on home/# (the # covers kitchen/temp) and the logger on home/kitchen/+ (the + swallows temp). A door-only app subscribed to home/door does not match. The broker sends one copy to each of the three matching subscribers, and because it's QoS 1, each returns a PUBACK so the broker knows the delivery landed.

Check yourself

A subscriber's filter is home/#. A sensor publishes to home/kitchen/temp. Does it match?

A subscriber's filter is home/+. A sensor publishes to home/kitchen/temp. Does it match?