Tutoriais

MQTT em C++: exemplos com Paho C++ e async_mqtt

Use MQTT em C++: instale o Paho C++, publique e assine com async_client, adicione TLS, reconexão automática e MQTT 5, e veja quando o async_mqtt é melhor.

Atualizado · 9 min de leitura

MQTT em C++: exemplos com Paho C++ e async_mqtt

A forma mais rápida de usar MQTT em C++ é o cliente Eclipse Paho C++ (paho.mqtt.cpp): crie um mqtt::async_client, conecte com um connect_options_builder e depois chame publish() e subscribe() — cada chamada retorna um token no qual você pode fazer wait(). Se a sua aplicação já roda sobre Boost.Asio, o async_mqtt é uma alternativa header-only que se encaixa nesse modelo de forma nativa.

Este tutorial monta um publisher e um subscriber com Paho C++, adiciona TLS, reconexões e MQTT 5, e termina mostrando como testar tudo contra um broker real.

Escolhendo uma biblioteca

BibliotecaModeloProtocoloBom para
Paho C++ (paho.mqtt.cpp)Wrapper C++ fino sobre a biblioteca assíncrona Paho C; tokens, callbacks ou fila de consumoMQTT 3.1, 3.1.1, 5; TCP, TLS, WebSocketA maioria das aplicações, gateways Linux, ferramentas desktop
async_mqttHeader-only, completion tokens do Boost.Asio (callbacks, futures, corrotinas C++20)MQTT 3.1.1 e 5; TCP, TLS, WebSocketServidores baseados em Asio, muitas conexões, quando você também quer peças para um broker/endpoint
Paho C (paho.mqtt.c)API em C puroMQTT 3.1.1 e 5Projetos em C, ou quando você não pode usar C++ — veja MQTT em C com Paho

Para microcontroladores, nenhuma das duas é a escolha usual; bibliotecas C embarcadas como coreMQTT ou o cliente do ESP-IDF são mais leves. O restante deste guia mira hosts Linux, macOS e Windows. Para uma visão mais ampla das bibliotecas cliente por linguagem, veja as melhores bibliotecas MQTT.

Instalar o Paho C++

O Paho C++ precisa da biblioteca Paho C (compilada com SSL se você quiser TLS). Três caminhos comuns:

Opções de instalação
# 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

Um CMakeLists.txt mínimo vincula o target importado, que já traz a biblioteca C e o OpenSSL para você:

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)

Se você linkar manualmente e vir undefined reference to MQTTAsync_create, está faltando a biblioteca C: linke paho-mqtt3as (a variante assíncrona com SSL) depois de paho-mqttpp3.

Publicar uma mensagem

Toda chamada assíncrona retorna um token. Chamar ->wait() a transforma em uma chamada bloqueante, que é exatamente o que você quer em uma ferramenta pequena:

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;
    }
}

Com QoS 1, o wait() no token de publicação só retorna depois que o broker confirmou a mensagem. Com QoS 0, retorna assim que o pacote é escrito. Veja os níveis de QoS do MQTT para saber quando cada um vale a pena.

Assinar com a fila de consumo

O subscriber mais simples evita callbacks por completo: start_consuming() coloca as mensagens recebidas em uma fila thread-safe que você lê no seu próprio loop.

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;
    }
}

Rode o subscriber e depois o publisher em um segundo terminal. Você também pode publicar em testmqtt/cpp/hello pelo cliente MQTT online e ver a linha aparecer no seu terminal.

Callbacks e reconexão automática

Serviços de longa duração geralmente preferem callbacks e precisam sobreviver a quedas de rede. O Paho C++ tem setters com lambdas para cada evento, e automatic_reconnect() cuida do 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));
}

Três regras mantêm o código com callbacks saudável:

  • Nunca bloqueie em um callback. Chamar ->wait() dentro de connected ou message_arrived espera na mesma thread que deveria completar o token. Dispare a chamada e siga em frente, ou envie o trabalho para a sua própria fila.
  • Refaça as assinaturas ao conectar. O connected handler roda após cada (re)conexão bem-sucedida, então as assinaturas voltam mesmo que o broker tenha perdido a sessão.
  • Use um client ID estável e único com clean_session(false). Dois processos com o mesmo ID derrubam um ao outro em loop. Gere um com o gerador de client ID e leia as boas práticas de reconexão para orientações sobre back-off.

Sessões persistentes exigem um broker que as armazene: o endpoint compartilhado broker.connectmq.com não guarda sessões offline, então teste clean_session(false) contra o seu próprio broker privado. No endpoint compartilhado, é a nova assinatura no connected handler que mantém o serviço funcionando.

TLS e autenticação

Para qualquer coisa além de um teste rápido, conecte via TLS na porta 8883 com credenciais. Troque o esquema da URI para ssl:// e adicione 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();
}

O caminho do bundle de CAs varia conforme o sistema (/etc/pki/tls/certs/ca-bundle.crt na família RHEL; no macOS, exporte um ou aponte para o bundle do OpenSSL do Homebrew). Se a conexão falhar antes do CONNACK, verifique a cadeia de certificados a partir da mesma máquina com os passos de testar uma conexão MQTT com TLS.

MQTT 5 com Paho C++

O MQTT 5 precisa ser ativado explicitamente. Crie o cliente com opções de MQTT 5 e use o builder v5, que também aceita propriedades:

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();

Com MQTT 5 o broker retorna reason codes em vez dos antigos return codes de um byte, então uma falha diz muito mais (por exemplo 0x87 Not authorized ou 0x97 Quota exceeded). Consulte qualquer código na ferramenta de reason codes MQTT. As diferenças entre as versões estão resumidas em MQTT 5 vs 3.1.1.

async_mqtt: a opção nativa do Asio

O async_mqtt é uma biblioteca header-only em C++17 construída sobre Boost.Asio, do autor do antigo mqtt_cpp. Cada operação recebe um completion token do Asio, então a mesma chamada pode usar um callback, um std::future ou co_await em uma corrotina C++20. Ele suporta MQTT 3.1.1 e 5 sobre TCP, TLS e WebSocket, e também fornece as peças para um broker ou endpoint de servidor.

Escolha-o quando:

  • o seu programa já roda um io_context (um servidor baseado em Asio, um gateway com muitos sockets),
  • você quer corrotinas em vez de cadeias de callbacks,
  • você prefere uma dependência header-only a uma biblioteca C compilada.

A API mudou entre versões major, então comece pelos exemplos do repositório que correspondem à versão que você instalar, e não por snippets antigos. Conceitualmente o fluxo é o mesmo do Paho: abrir o transporte, enviar CONNECT e esperar o CONNACK, enviar SUBSCRIBE e então iterar recebendo pacotes PUBLISH.

Erros comuns

MensagemCausa provávelCorreção
MQTT error [-1]: TCP/TLS connect failureHost ou porta errados, firewall ou falha no handshake TLSConfira se o esquema da URI corresponde à porta (tcp:// 1883, ssl:// 8883) e teste a porta a partir da mesma máquina
A conexão falha na hora com uma URI ssl://Paho C compilado sem suporte a SSLRecompile com -DPAHO_WITH_SSL=ON ou use um pacote compilado com OpenSSL
Connection lost a cada poucos segundosOutro cliente usa o mesmo client IDDê a cada processo um ID único
undefined reference to MQTTAsync_*Biblioteca C não linkadaLinke PahoMqttCpp::paho-mqttpp3 via CMake ou adicione paho-mqtt3as
CONNACK recusado (rc 4/5, ou 0x86/0x87 no MQTT 5)Credenciais erradas ou sem autorizaçãoVerifique usuário, senha e ACLs

Teste de ponta a ponta

Antes de culpar o seu código, prove que o lado do broker funciona com um cliente confiável. Inicie um subscriber no terminal e rode o seu publisher:

Veja o que o seu programa C++ publica
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/#' -v

Depois inverta: rode o seu subscriber C++ e envie uma mensagem pela linha de comando:

Envie um comando para o serviço C++
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/commands/led' -m 'on' -q 1

Para exercitar as reconexões, desative a interface de rede por mais tempo que o intervalo de keep-alive e confirme que o connection-lost handler dispara, que o cliente reconecta e que as mensagens voltam a fluir. Se você precisa de código inicial em outra linguagem para o outro lado do teste, o gerador de código MQTT cria para você.

Veja os topics de C++ no cliente online

O Paho C++ entrega um cliente MQTT confiável com muito pouco código: um builder para as opções, tokens para o fluxo de controle e callbacks ou uma fila de consumo para as mensagens recebidas. Mantenha os callbacks sem bloqueio, ative a reconexão automática com client IDs estáveis, use TLS desde o início e recorra ao async_mqtt quando o seu programa já for uma aplicação Asio.

Perguntas frequentes

Qual biblioteca MQTT devo usar em C++?

Para a maioria das aplicações, o Eclipse Paho C++ (paho.mqtt.cpp): é maduro, suporta MQTT 3.1.1 e 5, TLS e WebSockets, e encapsula a popular biblioteca Paho C. O async_mqtt é uma boa escolha se o seu código já é baseado em Boost.Asio e você quer uma biblioteca header-only com suporte a corrotinas.

Por que meu programa com Paho C++ trava dentro de um callback?

Provavelmente você chamou ->wait() em um token dentro de um callback como message_arrived ou connected. Os callbacks rodam na thread da biblioteca, então bloquear ali é esperar por si mesmo. Dispare a operação sem esperar, ou passe o trabalho para outra thread ou para a fila de consumo.

O Paho C++ reconecta automaticamente?

Sim, se você ativar automatic_reconnect() no connect_options_builder. Combine com uma sessão persistente (clean_session(false) e um client ID fixo) ou refaça as assinaturas no connected handler; caso contrário, você reconecta sem nenhuma assinatura.