Tutoriais

MQTT em C com Eclipse Paho C: exemplos de cliente síncrono e assíncrono

Use MQTT em C com a biblioteca Eclipse Paho C: instale ou compile, publisher síncrono, subscriber assíncrono com reconexão, TLS, MQTT 5 e códigos de retorno.

Atualizado · 10 min de leitura

MQTT em C com Eclipse Paho C: exemplos de cliente síncrono e assíncrono

Eclipse Paho C é a biblioteca cliente MQTT de referência para C em Linux, macOS e Windows. Ela oferece duas APIs: MQTTClient, uma API síncrona em que cada chamada bloqueia até terminar, e MQTTAsync, uma API não bloqueante baseada em callbacks com reconexão automática embutida. As duas suportam MQTT 3.1.1 e MQTT 5, TLS via OpenSSL e WebSockets. Este tutorial instala a biblioteca, monta um publisher síncrono e um subscriber assíncrono e depois adiciona TLS e MQTT 5.

Para microcontroladores sem sistema operacional, o Paho C costuma ser pesado demais; veja coreMQTT no FreeRTOS.

Instalar ou compilar a biblioteca

Pacotes são o caminho mais rápido:

Instalar pelos pacotes
# Debian / Ubuntu
sudo apt install libpaho-mqtt-dev

# macOS (Homebrew)
brew install libpaho-mqtt

Para a versão mais recente, ou para controlar as opções de build, compile com CMake:

Compilar do código-fonte com TLS
git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c
cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_ENABLE_TESTING=OFF
sudo cmake --build build/ --target install
sudo ldconfig   # só no Linux

Você precisa de um compilador C, CMake e do pacote de desenvolvimento do OpenSSL (libssl-dev) para PAHO_WITH_SSL=ON. O build gera quatro bibliotecas; escolha a que combina com seu programa:

BibliotecaAPITLS
paho-mqtt3cMQTTClient (síncrona)Não
paho-mqtt3csMQTTClient (síncrona)Sim
paho-mqtt3aMQTTAsyncNão
paho-mqtt3asMQTTAsyncSim

Um publisher síncrono

A API síncrona se lê de cima para baixo: criar, conectar, publicar, esperar a entrega, desconectar.

pub.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"

#define ADDRESS  "tcp://broker.connectmq.com:1883"
#define CLIENTID "testmqtt-c-sync-pub"
#define TOPIC    "testmqtt/c-paho/hello"
#define TIMEOUT  10000L

int main(void)
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_message msg = MQTTClient_message_initializer;
    MQTTClient_deliveryToken token;
    int rc;

    rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
                           MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (rc != MQTTCLIENT_SUCCESS) {
        fprintf(stderr, "falha no create: %d\n", rc);
        return EXIT_FAILURE;
    }

    conn_opts.keepAliveInterval = 30;
    conn_opts.cleansession = 1;
    conn_opts.username = getenv("MQTT_USER");   /* NULL serve para brokers abertos */
    conn_opts.password = getenv("MQTT_PASS");

    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) {
        fprintf(stderr, "falha na conexão: %d (%s)\n", rc, MQTTClient_strerror(rc));
        MQTTClient_destroy(&client);
        return EXIT_FAILURE;
    }

    const char *payload = "hello from Paho C";
    msg.payload = (void *)payload;
    msg.payloadlen = (int)strlen(payload);
    msg.qos = 1;
    msg.retained = 0;

    MQTTClient_publishMessage(client, TOPIC, &msg, &token);
    rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
    printf("entrega %s (token %d)\n", rc == MQTTCLIENT_SUCCESS ? "confirmada" : "falhou", token);

    MQTTClient_disconnect(client, 1000);
    MQTTClient_destroy(&client);
    return rc == MQTTCLIENT_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;
}
Compilar e executar
gcc pub.c -o pub -lpaho-mqtt3c
./pub

Antes de rodar, abra o cliente MQTT online e se inscreva em testmqtt/c-paho/# — a mensagem aparece lá assim que o pub imprime "entrega confirmada". Com QoS 1, MQTTClient_waitForCompletion só retorna depois do PUBACK do broker, então sucesso significa mesmo que o broker recebeu a mensagem.

Um subscriber assíncrono com reconexão automática

Para um processo de longa duração, MQTTAsync é a melhor escolha. Nada bloqueia; você registra callbacks e a thread em segundo plano da biblioteca os chama. O ponto importante é o callback connected: ele roda depois de cada conexão bem-sucedida, incluindo reconexões automáticas, então é o lugar certo para fazer o subscribe.

sub_async.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "MQTTAsync.h"

#define ADDRESS  "tcp://broker.connectmq.com:1883"
#define CLIENTID "testmqtt-c-async-sub"
#define TOPIC    "testmqtt/c-paho/#"

static void on_subscribe(void *ctx, MQTTAsync_successData *resp)
{
    printf("inscrito em %s\n", TOPIC);
}

static void on_subscribe_failure(void *ctx, MQTTAsync_failureData *resp)
{
    printf("falha no subscribe: %d\n", resp ? resp->code : 0);
}

/* Roda após a primeira conexão e após cada reconexão automática. */
static void on_connected(void *ctx, char *cause)
{
    MQTTAsync client = (MQTTAsync)ctx;
    MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
    opts.onSuccess = on_subscribe;
    opts.onFailure = on_subscribe_failure;
    opts.context = client;
    printf("conectado (%s)\n", cause ? cause : "");
    MQTTAsync_subscribe(client, TOPIC, 1, &opts);
}

static void on_connect_failure(void *ctx, MQTTAsync_failureData *resp)
{
    printf("falha na conexão: %d %s\n", resp ? resp->code : 0,
           resp && resp->message ? resp->message : "");
}

static void on_connection_lost(void *ctx, char *cause)
{
    printf("conexão perdida: %s\n", cause ? cause : "desconhecida");
}

static int on_message(void *ctx, char *topic, int topic_len, MQTTAsync_message *m)
{
    printf("recebido %s: %.*s\n", topic, m->payloadlen, (char *)m->payload);
    MQTTAsync_freeMessage(&m);
    MQTTAsync_free(topic);
    return 1;   /* 1 = mensagem tratada */
}

int main(void)
{
    MQTTAsync client;
    MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;
    MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer;
    int rc;

    if ((rc = MQTTAsync_create(&client, ADDRESS, CLIENTID,
                               MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTASYNC_SUCCESS) {
        fprintf(stderr, "falha no create: %d\n", rc);
        return EXIT_FAILURE;
    }
    MQTTAsync_setCallbacks(client, client, on_connection_lost, on_message, NULL);
    MQTTAsync_setConnected(client, client, on_connected);

    conn_opts.keepAliveInterval = 30;
    conn_opts.cleansession = 1;
    conn_opts.automaticReconnect = 1;
    conn_opts.minRetryInterval = 1;    /* segundos, dobra até o máximo */
    conn_opts.maxRetryInterval = 30;
    conn_opts.onFailure = on_connect_failure;
    conn_opts.context = client;
    conn_opts.username = getenv("MQTT_USER");
    conn_opts.password = getenv("MQTT_PASS");

    if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) {
        fprintf(stderr, "falha ao iniciar a conexão: %d\n", rc);
        return EXIT_FAILURE;
    }

    printf("pressione Enter para sair\n");
    getchar();

    MQTTAsync_disconnect(client, &disc_opts);
    sleep(1);
    MQTTAsync_destroy(&client);
    return EXIT_SUCCESS;
}
Compilar e executar
gcc sub_async.c -o sub_async -lpaho-mqtt3a
./sub_async

Rode ./pub em outro terminal, ou publique pelo cliente no navegador, e o subscriber imprime cada mensagem. Três regras mantêm saudável o código com MQTTAsync:

  • Libere o que a biblioteca entrega. O callback de mensagens precisa chamar MQTTAsync_freeMessage e MQTTAsync_free(topic), senão o processo vaza memória a cada mensagem.
  • Saia rápido dos callbacks. Eles rodam na thread da biblioteca; trabalho demorado ali atrasa o tratamento do keep-alive. Passe as mensagens para sua própria fila ou worker.
  • Faça o subscribe no callback connected. Com clean session o broker esquece as inscrições na desconexão; refazê-las ali as restaura após cada reconexão automática.

Compilando com CMake

A biblioteca instalada traz um pacote CMake, então projetos maiores não precisam de flags de link escritas à mão:

CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(mqtt_c_demo C)

find_package(eclipse-paho-mqtt-c REQUIRED)

add_executable(pub pub.c)
target_link_libraries(pub eclipse-paho-mqtt-c::paho-mqtt3c)

add_executable(sub_async sub_async.c)
target_link_libraries(sub_async eclipse-paho-mqtt-c::paho-mqtt3a)

Use os targets paho-mqtt3cs / paho-mqtt3as quando adicionar TLS.

TLS e credenciais

Mude o esquema da URI para ssl:// e a porta para 8883, preencha as opções SSL e linke a variante TLS da biblioteca:

Opções TLS (API síncrona)
#define ADDRESS "ssl://broker.connectmq.com:8883"

MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
ssl_opts.enableServerCertAuth = 1;
ssl_opts.verify = 1;                 /* verificar o hostname no certificado */
/* ssl_opts.trustStore = "ca.pem";      só para CA privada */
/* ssl_opts.keyStore = "client.pem";    certificado de cliente (TLS mútuo) */
conn_opts.ssl = &ssl_opts;
Linkar a biblioteca TLS
gcc pub_tls.c -o pub_tls -lpaho-mqtt3cs

Sem trustStore, são usados os locais de certificados padrão do OpenSSL, o que funciona com brokers que têm certificados publicamente confiáveis. A API assíncrona tem os mesmos campos em MQTTAsync_SSLOptions. Quando o handshake falhar, descarte primeiro a cadeia de certificados seguindo os passos de testar uma conexão MQTT TLS.

MQTT 5 com Paho C

O MQTT 5 precisa de uma opção de criação, de um inicializador v5 para as opções de conexão e das variantes ...5 das chamadas, que retornam reason codes:

Conexão MQTT 5 (API síncrona)
MQTTClient_createOptions create_opts = MQTTClient_createOptions_initializer;
create_opts.MQTTVersion = MQTTVERSION_5;
MQTTClient_createWithOptions(&client, ADDRESS, CLIENTID,
                             MQTTCLIENT_PERSISTENCE_NONE, NULL, &create_opts);

MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer5;
conn_opts.cleanstart = 1;
MQTTProperties props = MQTTProperties_initializer;

MQTTResponse resp = MQTTClient_connect5(client, &conn_opts, &props, NULL);
if (resp.reasonCode != MQTTREASONCODE_SUCCESS)
    fprintf(stderr, "connect: %s\n", MQTTReasonCode_toString(resp.reasonCode));
MQTTResponse_free(resp);

Publicar e se inscrever funciona do mesmo jeito via MQTTClient_publishMessage5 e MQTTClient_subscribe5. Consulte qualquer reason code na referência de reason codes MQTT.

Códigos de retorno que você vai ver

CódigoSignificadoCorreção típica
-1 (MQTTCLIENT_FAILURE)Falha genérica, muitas vezes host inacessível ou handshake TLS com falhaVerifique host, porta, firewall e certificados
-10 (MQTTCLIENT_SSL_NOT_SUPPORTED)URI ssl:// com uma biblioteca sem TLSLinke paho-mqtt3cs / paho-mqtt3as
-14 (MQTTCLIENT_BAD_PROTOCOL)Esquema de URI desconhecidoUse tcp://, ssl://, ws:// ou wss://
4CONNACK: usuário ou senha incorretosVerifique as credenciais
5CONNACK: não autorizadoVerifique as ACLs do broker para este cliente

Os códigos positivos 1–5 são os códigos de retorno CONNACK do MQTT 3.1.1 vindos do broker, explicados em detalhe em reason codes do CONNACK. Imprima-os com MQTTClient_strerror para deixar os logs legíveis.

Testando seu cliente

  1. Rode sub_async, depois pub, e confirme a ida e volta; repita com o cliente online em um dos lados.
  2. Desconecte a rede por mais tempo que o keep-alive e reconecte: você deve ver "conexão perdida" e depois "conectado" e "inscrito" de novo — a reconexão automática funcionando. Boas práticas de reconexão cobre o backoff para frotas inteiras.
  3. Coloque um valor errado em MQTT_PASS e confirme que o callback de falha informa o problema.
  4. Rode com Valgrind ou AddressSanitizer por alguns milhares de mensagens para provar que o callback de mensagens libera tudo.

Inscreva-se no topic da demo pelo navegador

O Paho C é pequeno, portável e está em produção há anos. Comece com MQTTClient para ferramentas e testes, passe para MQTTAsync com reconexão automática em serviços, linke as bibliotecas s quando adicionar TLS e libere cada mensagem recebida. Se você precisa de C++ ou Rust por cima, o Paho C++ em MQTT em C++ e o paho-mqtt para Rust encapsulam esta biblioteca.

Perguntas frequentes

Qual a diferença entre MQTTClient e MQTTAsync no Paho C?

MQTTClient é a API síncrona: chamadas como MQTTClient_connect bloqueiam até terminar, o que deixa programas simples curtos. MQTTAsync nunca bloqueia; cada operação recebe callbacks de sucesso e falha, e ela adiciona recursos como reconexão automática e buffer offline. Use MQTTAsync para serviços de longa duração.

Qual biblioteca do Paho C devo linkar?

paho-mqtt3c para a API síncrona sem TLS, paho-mqtt3cs para síncrona com TLS, paho-mqtt3a para assíncrona sem TLS e paho-mqtt3as para assíncrona com TLS. Linkar uma biblioteca sem TLS e conectar a uma URI ssl:// falha com o código de retorno -10 (SSL não suportado).

O que significa o código de retorno 5 de MQTTClient_connect?

Os códigos positivos de 1 a 5 são os códigos de retorno CONNACK do MQTT 3.1.1 enviados pelo broker. 5 significa não autorizado e 4 usuário ou senha incorretos. Códigos negativos vêm da própria biblioteca, por exemplo -1 para uma falha genérica como host inacessível.