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:
# Debian / Ubuntu
sudo apt install libpaho-mqtt-dev
# macOS (Homebrew)
brew install libpaho-mqttPara a versão mais recente, ou para controlar as opções de build, compile com CMake:
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 LinuxVocê 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:
| Biblioteca | API | TLS |
|---|---|---|
paho-mqtt3c | MQTTClient (síncrona) | Não |
paho-mqtt3cs | MQTTClient (síncrona) | Sim |
paho-mqtt3a | MQTTAsync | Não |
paho-mqtt3as | MQTTAsync | Sim |
Um publisher síncrono
A API síncrona se lê de cima para baixo: criar, conectar, publicar, esperar a entrega, desconectar.
#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;
}gcc pub.c -o pub -lpaho-mqtt3c
./pubAntes 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.
#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;
}gcc sub_async.c -o sub_async -lpaho-mqtt3a
./sub_asyncRode ./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_freeMessageeMQTTAsync_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:
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:
#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;gcc pub_tls.c -o pub_tls -lpaho-mqtt3csSem 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:
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ódigo | Significado | Correção típica |
|---|---|---|
-1 (MQTTCLIENT_FAILURE) | Falha genérica, muitas vezes host inacessível ou handshake TLS com falha | Verifique host, porta, firewall e certificados |
-10 (MQTTCLIENT_SSL_NOT_SUPPORTED) | URI ssl:// com uma biblioteca sem TLS | Linke paho-mqtt3cs / paho-mqtt3as |
-14 (MQTTCLIENT_BAD_PROTOCOL) | Esquema de URI desconhecido | Use tcp://, ssl://, ws:// ou wss:// |
4 | CONNACK: usuário ou senha incorretos | Verifique as credenciais |
5 | CONNACK: não autorizado | Verifique 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
- Rode
sub_async, depoispub, e confirme a ida e volta; repita com o cliente online em um dos lados. - 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.
- Coloque um valor errado em
MQTT_PASSe confirme que o callback de falha informa o problema. - 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.


