La forma más rápida de usar MQTT en C++ es el cliente Eclipse Paho C++ (paho.mqtt.cpp): creas un mqtt::async_client, te conectas con un connect_options_builder y luego llamas a publish() y subscribe(); cada llamada devuelve un token sobre el que puedes hacer wait(). Si tu aplicación ya vive sobre Boost.Asio, async_mqtt es una alternativa header-only que encaja de forma nativa en ese modelo.
Este tutorial construye un publicador y un suscriptor con Paho C++, añade TLS, reconexiones y MQTT 5, y termina mostrando cómo probarlo todo contra un broker real.
Elegir una librería
| Librería | Modelo | Protocolo | Encaja bien en |
|---|---|---|---|
Paho C++ (paho.mqtt.cpp) | Wrapper C++ ligero sobre la librería asíncrona Paho C; tokens, callbacks o cola de consumo | MQTT 3.1, 3.1.1, 5; TCP, TLS, WebSocket | La mayoría de aplicaciones, gateways Linux, herramientas de escritorio |
| async_mqtt | Header-only, completion tokens de Boost.Asio (callbacks, futures, corrutinas C++20) | MQTT 3.1.1 y 5; TCP, TLS, WebSocket | Servidores basados en Asio, muchas conexiones, cuando también quieres piezas para un broker/endpoint |
Paho C (paho.mqtt.c) | API en C puro | MQTT 3.1.1 y 5 | Proyectos en C, o cuando no puedes usar C++; consulta MQTT en C con Paho |
Para microcontroladores ninguna de las dos es la elección habitual; librerías C embebidas como coreMQTT o el cliente de ESP-IDF son más ligeras. El resto de esta guía apunta a hosts Linux, macOS y Windows. Para una visión más amplia de las librerías cliente por lenguaje, consulta las mejores librerías MQTT.
Instalar Paho C++
Paho C++ necesita la librería Paho C (compilada con SSL si quieres TLS). Tres caminos habituales:
# Debian / Ubuntu (distribution packages can lag behind the latest release)
sudo apt install libpaho-mqtt-dev libpaho-mqttpp-dev
# vcpkg
vcpkg install paho-mqttpp3
# From source, building the bundled Paho C library with TLS
git clone https://github.com/eclipse/paho.mqtt.cpp
cd paho.mqtt.cpp && git submodule update --init
cmake -Bbuild -DPAHO_WITH_MQTT_C=ON -DPAHO_WITH_SSL=ON
sudo cmake --build build --target installUn CMakeLists.txt mínimo enlaza el target importado, que arrastra por ti la librería C y OpenSSL:
cmake_minimum_required(VERSION 3.16)
project(mqtt_demo CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(PahoMqttCpp REQUIRED)
add_executable(publish publish.cpp)
target_link_libraries(publish PRIVATE PahoMqttCpp::paho-mqttpp3)
add_executable(subscribe subscribe.cpp)
target_link_libraries(subscribe PRIVATE PahoMqttCpp::paho-mqttpp3)Si enlazas a mano y ves undefined reference to MQTTAsync_create, te falta la librería C: enlaza paho-mqtt3as (la variante asíncrona con SSL) después de paho-mqttpp3.
Publicar un mensaje
Cada llamada asíncrona devuelve un token. Llamar a ->wait() la convierte en bloqueante, que es justo lo que quieres en una herramienta pequeña:
#include <chrono>
#include <iostream>
#include <mqtt/async_client.h>
int main() {
const std::string server = "tcp://broker.connectmq.com:1883";
mqtt::async_client client(server, "testmqtt-cpp-pub");
auto opts = mqtt::connect_options_builder()
.clean_session()
.keep_alive_interval(std::chrono::seconds(30))
.finalize();
try {
client.connect(opts)->wait();
auto msg = mqtt::make_message("testmqtt/cpp/hello", "Hello from C++", 1, false);
client.publish(msg)->wait(); // returns after PUBACK for QoS 1
std::cout << "published" << std::endl;
client.disconnect()->wait();
} catch (const mqtt::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}Con QoS 1, el wait() sobre el token de publicación solo vuelve cuando el broker ha confirmado el mensaje. Con QoS 0 vuelve en cuanto el paquete se escribe. Consulta los niveles de QoS de MQTT para saber cuándo merece la pena cada uno.
Suscribirse con la cola de consumo
El suscriptor más sencillo evita los callbacks por completo: start_consuming() coloca los mensajes entrantes en una cola thread-safe que lees en tu propio bucle.
#include <iostream>
#include <mqtt/async_client.h>
int main() {
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-sub");
auto opts = mqtt::connect_options_builder()
.clean_session()
.finalize();
try {
client.start_consuming(); // before connect, so nothing is missed
client.connect(opts)->wait();
client.subscribe("testmqtt/cpp/#", 1)->wait();
std::cout << "waiting for messages..." << std::endl;
while (true) {
auto msg = client.consume_message(); // blocks
if (!msg) break; // nullptr: connection lost
std::cout << msg->get_topic() << " -> " << msg->to_string() << std::endl;
}
} catch (const mqtt::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}Ejecuta el suscriptor y luego el publicador en una segunda terminal. También puedes publicar en testmqtt/cpp/hello desde el cliente MQTT online y ver aparecer la línea en tu terminal.
Callbacks y reconexión automática
Los servicios de larga duración suelen preferir callbacks y deben sobrevivir a cortes de red. Paho C++ tiene setters con lambdas para cada evento, y automatic_reconnect() se encarga del back-off:
#include <chrono>
#include <iostream>
#include <thread>
#include <mqtt/async_client.h>
int main() {
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-service");
client.set_connected_handler([&client](const std::string&) {
std::cout << "connected, subscribing" << std::endl;
client.subscribe("testmqtt/cpp/commands/#", 1); // no wait() in a callback!
});
client.set_connection_lost_handler([](const std::string& cause) {
std::cerr << "connection lost: " << cause << std::endl;
});
client.set_message_callback([](mqtt::const_message_ptr msg) {
std::cout << msg->get_topic() << " " << msg->get_payload_str() << std::endl;
});
auto opts = mqtt::connect_options_builder()
.clean_session(false)
.keep_alive_interval(std::chrono::seconds(30))
.automatic_reconnect(std::chrono::seconds(1), std::chrono::seconds(30))
.finalize();
client.connect(opts)->wait();
while (true) std::this_thread::sleep_for(std::chrono::seconds(60));
}Tres reglas mantienen sano el código con callbacks:
- Nunca bloquees en un callback. Llamar a
->wait()dentro deconnectedomessage_arrivedespera en el mismo hilo que debería completar el token. Lanza la llamada y sigue, o envía el trabajo a tu propia cola. - Vuelve a suscribirte al conectar. El connected handler se ejecuta tras cada (re)conexión correcta, así que las suscripciones vuelven aunque el broker haya perdido la sesión.
- Usa un client ID estable y único con
clean_session(false). Dos procesos con el mismo ID se expulsan mutuamente en bucle. Genera uno con el generador de client ID y lee las buenas prácticas de reconexión para orientarte con el back-off.
Las sesiones persistentes necesitan un broker que las guarde: el endpoint compartido broker.connectmq.com no conserva sesiones offline, así que prueba clean_session(false) contra tu propio broker privado. En el endpoint compartido, lo que mantiene vivo el servicio es la resuscripción en el connected handler.
TLS y autenticación
Para cualquier cosa más allá de una prueba rápida, conéctate por TLS en el puerto 8883 con credenciales. Cambia el esquema de la URI a ssl:// y añade ssl_options:
#include <cstdlib>
#include <mqtt/async_client.h>
static std::string env(const char* name) {
const char* v = std::getenv(name);
return v ? v : "";
}
int main() {
mqtt::async_client client("ssl://broker.connectmq.com:8883", "testmqtt-cpp-tls");
auto ssl = mqtt::ssl_options_builder()
.trust_store("/etc/ssl/certs/ca-certificates.crt") // system CA bundle
.enable_server_cert_auth(true)
.finalize();
auto opts = mqtt::connect_options_builder()
.user_name(env("MQTT_USER"))
.password(env("MQTT_PASS"))
.ssl(std::move(ssl))
.finalize();
client.connect(opts)->wait();
client.publish("testmqtt/cpp/secure", "over TLS", 1, false)->wait();
client.disconnect()->wait();
}La ruta del bundle de CAs cambia según el sistema (/etc/pki/tls/certs/ca-bundle.crt en la familia RHEL; en macOS exporta uno o apunta al bundle del OpenSSL de Homebrew). Si la conexión falla antes del CONNACK, verifica la cadena de certificados desde la misma máquina con los pasos de probar una conexión MQTT con TLS.
MQTT 5 con Paho C++
MQTT 5 hay que activarlo explícitamente. Crea el cliente con opciones de MQTT 5 y usa el builder v5, que además acepta propiedades:
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-v5",
mqtt::create_options(MQTTVERSION_5));
auto opts = mqtt::connect_options_builder::v5()
.clean_start(false)
.properties({{mqtt::property::SESSION_EXPIRY_INTERVAL, 3600}})
.finalize();
client.connect(opts)->wait();Con MQTT 5 el broker devuelve reason codes en lugar de los antiguos return codes de un byte, así que un fallo te dice mucho más (por ejemplo 0x87 Not authorized o 0x97 Quota exceeded). Busca cualquier código en la herramienta de reason codes MQTT. Las diferencias entre versiones están resumidas en MQTT 5 vs 3.1.1.
async_mqtt: la opción nativa de Asio
async_mqtt es una librería header-only en C++17 construida sobre Boost.Asio, del autor del antiguo mqtt_cpp. Cada operación recibe un completion token de Asio, de modo que la misma llamada puede usar un callback, un std::future o co_await en una corrutina C++20. Soporta MQTT 3.1.1 y 5 sobre TCP, TLS y WebSocket, y también ofrece las piezas para construir un broker o un endpoint de servidor.
Elígela cuando:
- tu programa ya ejecuta un
io_context(un servidor basado en Asio, un gateway con muchos sockets), - prefieres corrutinas a cadenas de callbacks,
- prefieres una dependencia header-only a una librería C compilada.
Su API ha cambiado entre versiones mayores, así que parte de los ejemplos del repositorio que corresponden a la versión que instales, no de snippets antiguos. Conceptualmente el flujo es el mismo que en Paho: abrir el transporte, enviar CONNECT y esperar el CONNACK, enviar SUBSCRIBE y después iterar recibiendo paquetes PUBLISH.
Errores comunes
| Mensaje | Causa probable | Solución |
|---|---|---|
MQTT error [-1]: TCP/TLS connect failure | Host o puerto incorrectos, firewall o fallo del handshake TLS | Comprueba que el esquema de la URI coincide con el puerto (tcp:// 1883, ssl:// 8883) y prueba el puerto desde la misma máquina |
La conexión falla al instante con una URI ssl:// | Paho C compilado sin soporte SSL | Recompila con -DPAHO_WITH_SSL=ON o usa un paquete compilado con OpenSSL |
| Connection lost cada pocos segundos | Otro cliente usa el mismo client ID | Da a cada proceso un ID único |
undefined reference to MQTTAsync_* | La librería C no está enlazada | Enlaza PahoMqttCpp::paho-mqttpp3 con CMake o añade paho-mqtt3as |
CONNACK rechazado (rc 4/5, o 0x86/0x87 en MQTT 5) | Credenciales incorrectas o sin autorización | Verifica usuario, contraseña y ACLs |
Pruébalo de extremo a extremo
Antes de culpar a tu código, demuestra que el lado del broker funciona con un cliente de confianza. Arranca un suscriptor en la terminal y ejecuta tu publicador:
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/#' -vDespués, al revés: ejecuta tu suscriptor C++ y envía un mensaje desde la línea de comandos:
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/commands/led' -m 'on' -q 1Para probar las reconexiones, desactiva tu interfaz de red durante más tiempo que el intervalo de keep-alive y confirma que se dispara el connection-lost handler, que el cliente se reconecta y que los mensajes vuelven a fluir. Si necesitas código base en otro lenguaje para el otro lado de la prueba, el generador de código MQTT te lo genera.
Ver los topics de C++ en el cliente online
Paho C++ te da un cliente MQTT fiable con muy poco código: un builder para las opciones, tokens para el flujo de control y callbacks o una cola de consumo para los mensajes entrantes. Mantén los callbacks sin bloqueos, activa la reconexión automática con client IDs estables, usa TLS desde el principio y recurre a async_mqtt cuando tu programa ya sea una aplicación Asio.
Preguntas frecuentes
¿Qué librería MQTT debería usar en C++?
Para la mayoría de aplicaciones, Eclipse Paho C++ (paho.mqtt.cpp): es madura, soporta MQTT 3.1.1 y 5, TLS y WebSockets, y envuelve la popular librería Paho C. async_mqtt es buena opción si tu código ya se basa en Boost.Asio y quieres una librería header-only con soporte de corrutinas.
¿Por qué mi programa con Paho C++ se congela dentro de un callback?
Probablemente llamaste a ->wait() sobre un token dentro de un callback como message_arrived o connected. Los callbacks se ejecutan en el hilo de la librería, así que bloquear ahí es esperarse a sí mismo. Lanza la operación sin esperar, o pasa el trabajo a otro hilo o a la cola de consumo.
¿Paho C++ se reconecta automáticamente?
Sí, si activas automatic_reconnect() en connect_options_builder. Combínalo con una sesión persistente (clean_session(false) y un client ID fijo) o vuelve a suscribirte en el connected handler; si no, te reconectas sin ninguna suscripción.


