Tutoriels

MQTT en C++ : exemples avec Paho C++ et async_mqtt

Utilisez MQTT en C++ : installez Paho C++, publiez et abonnez-vous avec async_client, ajoutez TLS, reconnexion et MQTT 5, et voyez quand préférer async_mqtt.

Mis à jour · 9 min de lecture

MQTT en C++ : exemples avec Paho C++ et async_mqtt

Le moyen le plus rapide d'utiliser MQTT en C++ est le client Eclipse Paho C++ (paho.mqtt.cpp) : créez un mqtt::async_client, connectez-vous avec un connect_options_builder, puis appelez publish() et subscribe() — chaque appel renvoie un token sur lequel vous pouvez faire wait(). Si votre application tourne déjà sur Boost.Asio, async_mqtt est une alternative header-only qui s'intègre nativement à ce modèle.

Ce tutoriel construit un publisher et un subscriber avec Paho C++, ajoute TLS, les reconnexions et MQTT 5, puis montre comment tout tester contre un vrai broker.

Choisir une bibliothèque

BibliothèqueModèleProtocoleIdéale pour
Paho C++ (paho.mqtt.cpp)Fine surcouche C++ de la bibliothèque asynchrone Paho C ; tokens, callbacks ou file de consommationMQTT 3.1, 3.1.1, 5 ; TCP, TLS, WebSocketLa plupart des applications, passerelles Linux, outils desktop
async_mqttHeader-only, completion tokens Boost.Asio (callbacks, futures, coroutines C++20)MQTT 3.1.1 et 5 ; TCP, TLS, WebSocketServeurs basés sur Asio, nombreuses connexions, quand vous voulez aussi des briques pour un broker/endpoint
Paho C (paho.mqtt.c)API en C purMQTT 3.1.1 et 5Projets en C, ou quand le C++ n'est pas possible — voir MQTT en C avec Paho

Pour les microcontrôleurs, aucune des deux n'est le choix habituel ; des bibliothèques C embarquées comme coreMQTT ou le client d'ESP-IDF sont plus légères. La suite de ce guide vise les hôtes Linux, macOS et Windows. Pour un panorama plus large des bibliothèques clientes par langage, consultez les meilleures bibliothèques MQTT.

Installer Paho C++

Paho C++ a besoin de la bibliothèque Paho C (compilée avec SSL si vous voulez TLS). Trois approches courantes :

Options d'installation
# 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 install

Un CMakeLists.txt minimal lie la cible importée, qui embarque pour vous la bibliothèque C et OpenSSL :

CMakeLists.txt
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 vous faites l'édition de liens à la main et voyez undefined reference to MQTTAsync_create, il manque la bibliothèque C : liez paho-mqtt3as (la variante asynchrone avec SSL) après paho-mqttpp3.

Publier un message

Chaque appel asynchrone renvoie un token. Appeler ->wait() le transforme en appel bloquant, ce qui est exactement ce qu'il vous faut dans un petit outil :

publish.cpp
#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;
    }
}

En QoS 1, le wait() sur le token de publication ne rend la main qu'une fois le message acquitté par le broker. En QoS 0, il revient dès que le paquet est écrit. Consultez les niveaux de QoS MQTT pour savoir quand chacun en vaut la peine.

S'abonner avec la file de consommation

Le subscriber le plus simple se passe complètement de callbacks : start_consuming() place les messages entrants dans une file thread-safe que vous lisez dans votre propre boucle.

subscribe.cpp
#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;
    }
}

Lancez le subscriber, puis le publisher dans un second terminal. Vous pouvez aussi publier sur testmqtt/cpp/hello depuis le client MQTT en ligne et voir la ligne apparaître dans votre terminal.

Callbacks et reconnexion automatique

Les services de longue durée préfèrent généralement les callbacks et doivent survivre aux coupures réseau. Paho C++ propose des setters à base de lambdas pour chaque événement, et automatic_reconnect() gère le back-off :

service.cpp
#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));
}

Trois règles gardent le code à base de callbacks en bonne santé :

  • Ne bloquez jamais dans un callback. Appeler ->wait() dans connected ou message_arrived attend sur le thread même qui doit compléter le token. Lancez l'appel et continuez, ou transférez le travail vers votre propre file.
  • Réabonnez-vous à la connexion. Le connected handler s'exécute après chaque (re)connexion réussie, donc les abonnements reviennent même si le broker a perdu la session.
  • Utilisez un client ID stable et unique avec clean_session(false). Deux processus avec le même ID s'éjectent mutuellement en boucle. Générez-en un avec le générateur de client ID et lisez les bonnes pratiques de reconnexion pour le back-off.

Les sessions persistantes exigent un broker qui les conserve : l'endpoint partagé broker.connectmq.com ne garde aucune session hors ligne, testez donc clean_session(false) contre votre propre broker privé. Sur l'endpoint partagé, c'est le réabonnement dans le connected handler qui maintient le service en marche.

TLS et authentification

Pour tout ce qui dépasse un test rapide, connectez-vous en TLS sur le port 8883 avec des identifiants. Passez le schéma de l'URI à ssl:// et ajoutez des ssl_options :

tls.cpp
#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();
}

Le chemin du bundle de CA dépend du système (/etc/pki/tls/certs/ca-bundle.crt sur la famille RHEL ; sur macOS, exportez-en un ou pointez vers le bundle de l'OpenSSL de Homebrew). Si la connexion échoue avant le CONNACK, vérifiez la chaîne de certificats depuis la même machine en suivant tester une connexion MQTT TLS.

MQTT 5 avec Paho C++

MQTT 5 doit être activé explicitement. Créez le client avec les options MQTT 5 et utilisez le builder v5, qui accepte aussi des propriétés :

mqtt5.cpp
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();

Avec MQTT 5, le broker renvoie des reason codes au lieu des anciens return codes sur un octet, donc un échec vous en dit beaucoup plus (par exemple 0x87 Not authorized ou 0x97 Quota exceeded). Retrouvez n'importe quel code dans l'outil des reason codes MQTT. Les différences entre versions sont résumées dans MQTT 5 vs 3.1.1.

async_mqtt : l'option native Asio

async_mqtt est une bibliothèque header-only en C++17 bâtie sur Boost.Asio, par l'auteur de l'ancien mqtt_cpp. Chaque opération prend un completion token Asio, si bien que le même appel peut utiliser un callback, un std::future ou co_await dans une coroutine C++20. Elle prend en charge MQTT 3.1.1 et 5 sur TCP, TLS et WebSocket, et fournit aussi les briques d'un broker ou d'un endpoint serveur.

Choisissez-la quand :

  • votre programme exécute déjà un io_context (un serveur basé sur Asio, une passerelle avec de nombreux sockets),
  • vous voulez des coroutines plutôt que des chaînes de callbacks,
  • vous préférez une dépendance header-only à une bibliothèque C compilée.

Son API a évolué entre versions majeures : partez des exemples du dépôt correspondant à la version que vous installez plutôt que d'anciens extraits. Sur le principe, le flux est le même qu'avec Paho : ouvrir le transport, envoyer CONNECT et attendre le CONNACK, envoyer SUBSCRIBE, puis boucler sur la réception des paquets PUBLISH.

Erreurs courantes

MessageCause probableCorrectif
MQTT error [-1]: TCP/TLS connect failureHôte ou port erroné, pare-feu ou échec du handshake TLSVérifiez que le schéma de l'URI correspond au port (tcp:// 1883, ssl:// 8883), puis testez le port depuis la même machine
La connexion échoue immédiatement avec une URI ssl://Paho C compilé sans prise en charge SSLRecompilez avec -DPAHO_WITH_SSL=ON ou utilisez un paquet compilé avec OpenSSL
Connection lost toutes les quelques secondesUn autre client utilise le même client IDDonnez un ID unique à chaque processus
undefined reference to MQTTAsync_*Bibliothèque C non liéeLiez PahoMqttCpp::paho-mqttpp3 via CMake, ou ajoutez paho-mqtt3as
CONNACK refusé (rc 4/5, ou 0x86/0x87 en MQTT 5)Identifiants erronés ou non autoriséVérifiez nom d'utilisateur, mot de passe et ACL

Tester de bout en bout

Avant d'accuser votre code, prouvez que le côté broker fonctionne avec un client éprouvé. Lancez un subscriber dans le terminal et exécutez votre publisher :

Voir ce que publie votre programme C++
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/#' -v

Puis inversez : lancez votre subscriber C++ et envoyez un message depuis la ligne de commande :

Envoyer une commande au service C++
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/commands/led' -m 'on' -q 1

Pour éprouver les reconnexions, désactivez votre interface réseau plus longtemps que l'intervalle de keep-alive et vérifiez que le connection-lost handler se déclenche, que le client se reconnecte et que les messages circulent à nouveau. S'il vous faut du code de départ dans un autre langage pour l'autre côté du test, le générateur de code MQTT le produit pour vous.

Observer les topics C++ dans le client en ligne

Paho C++ vous donne un client MQTT fiable avec très peu de code : un builder pour les options, des tokens pour le flux de contrôle et des callbacks ou une file de consommation pour les messages entrants. Gardez les callbacks non bloquants, activez la reconnexion automatique avec des client IDs stables, utilisez TLS dès le départ et tournez-vous vers async_mqtt lorsque votre programme est déjà une application Asio.

Questions fréquentes

Quelle bibliothèque MQTT utiliser en C++ ?

Pour la plupart des applications, Eclipse Paho C++ (paho.mqtt.cpp) : elle est mature, prend en charge MQTT 3.1.1 et 5, TLS et WebSockets, et encapsule la très répandue bibliothèque Paho C. async_mqtt est un bon choix si votre code repose déjà sur Boost.Asio et que vous voulez une bibliothèque header-only avec prise en charge des coroutines.

Pourquoi mon programme Paho C++ se bloque-t-il dans un callback ?

Vous avez probablement appelé ->wait() sur un token dans un callback comme message_arrived ou connected. Les callbacks s'exécutent sur le thread de la bibliothèque : bloquer à cet endroit revient à s'attendre soi-même. Lancez l'opération sans attendre, ou confiez le travail à un autre thread ou à la file de consommation.

Paho C++ se reconnecte-t-il automatiquement ?

Oui, si vous activez automatic_reconnect() dans connect_options_builder. Combinez-le avec une session persistante (clean_session(false) et un client ID fixe) ou réabonnez-vous dans le connected handler ; sinon vous vous reconnectez sans aucun abonnement.