Eclipse Paho C est la bibliothèque cliente MQTT de référence pour C sous Linux, macOS et Windows. Elle propose deux API : MQTTClient, une API synchrone où chaque appel bloque jusqu'à sa fin, et MQTTAsync, une API non bloquante pilotée par callbacks avec reconnexion automatique intégrée. Les deux prennent en charge MQTT 3.1.1 et MQTT 5, TLS via OpenSSL et les WebSockets. Ce tutoriel installe la bibliothèque, construit un publisher synchrone et un subscriber asynchrone, puis ajoute TLS et MQTT 5.
Pour les microcontrôleurs sans système d'exploitation, Paho C est généralement trop lourd ; tournez-vous plutôt vers coreMQTT sous FreeRTOS.
Installer ou compiler la bibliothèque
Les paquets sont la voie la plus rapide :
# Debian / Ubuntu
sudo apt install libpaho-mqtt-dev
# macOS (Homebrew)
brew install libpaho-mqttPour la dernière version, ou pour maîtriser les options de compilation, compilez avec 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 # Linux uniquementIl vous faut un compilateur C, CMake et le paquet de développement OpenSSL (libssl-dev) pour PAHO_WITH_SSL=ON. La compilation produit quatre bibliothèques ; choisissez celle qui correspond à votre programme :
| Bibliothèque | API | TLS |
|---|---|---|
paho-mqtt3c | MQTTClient (synchrone) | Non |
paho-mqtt3cs | MQTTClient (synchrone) | Oui |
paho-mqtt3a | MQTTAsync | Non |
paho-mqtt3as | MQTTAsync | Oui |
Un publisher synchrone
L'API synchrone se lit de haut en bas : créer, se connecter, publier, attendre la livraison, se déconnecter.
#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, "échec de create : %d\n", rc);
return EXIT_FAILURE;
}
conn_opts.keepAliveInterval = 30;
conn_opts.cleansession = 1;
conn_opts.username = getenv("MQTT_USER"); /* NULL convient aux brokers ouverts */
conn_opts.password = getenv("MQTT_PASS");
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) {
fprintf(stderr, "échec de connexion : %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("livraison %s (token %d)\n", rc == MQTTCLIENT_SUCCESS ? "confirmée" : "échouée", token);
MQTTClient_disconnect(client, 1000);
MQTTClient_destroy(&client);
return rc == MQTTCLIENT_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;
}gcc pub.c -o pub -lpaho-mqtt3c
./pubAvant de le lancer, ouvrez le client MQTT en ligne et abonnez-vous à testmqtt/c-paho/# : le message y apparaît dès que pub affiche "livraison confirmée". En QoS 1, MQTTClient_waitForCompletion ne rend la main qu'après le PUBACK du broker, donc un succès signifie vraiment que le broker a reçu le message.
Un subscriber asynchrone avec reconnexion automatique
Pour un processus de longue durée, MQTTAsync convient mieux. Rien ne bloque ; vous enregistrez des callbacks et le thread d'arrière-plan de la bibliothèque les appelle. L'élément clé est le callback connected : il s'exécute après chaque connexion réussie, reconnexions automatiques comprises, c'est donc le bon endroit pour s'abonner.
#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("abonné à %s\n", TOPIC);
}
static void on_subscribe_failure(void *ctx, MQTTAsync_failureData *resp)
{
printf("échec du subscribe : %d\n", resp ? resp->code : 0);
}
/* S'exécute après la première connexion et après chaque reconnexion automatique. */
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("connecté (%s)\n", cause ? cause : "");
MQTTAsync_subscribe(client, TOPIC, 1, &opts);
}
static void on_connect_failure(void *ctx, MQTTAsync_failureData *resp)
{
printf("échec de connexion : %d %s\n", resp ? resp->code : 0,
resp && resp->message ? resp->message : "");
}
static void on_connection_lost(void *ctx, char *cause)
{
printf("connexion perdue : %s\n", cause ? cause : "inconnue");
}
static int on_message(void *ctx, char *topic, int topic_len, MQTTAsync_message *m)
{
printf("reçu %s : %.*s\n", topic, m->payloadlen, (char *)m->payload);
MQTTAsync_freeMessage(&m);
MQTTAsync_free(topic);
return 1; /* 1 = message traité */
}
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, "échec de 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; /* secondes, doublé jusqu'au maximum */
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, "impossible de lancer la connexion : %d\n", rc);
return EXIT_FAILURE;
}
printf("appuyez sur Entrée pour quitter\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_asyncLancez ./pub dans un second terminal, ou publiez depuis le client du navigateur, et le subscriber affiche chaque message. Trois règles gardent le code MQTTAsync sain :
- Libérez ce que la bibliothèque vous donne. Le callback de message doit appeler
MQTTAsync_freeMessageetMQTTAsync_free(topic), sinon le processus fuit de la mémoire à chaque message. - Sortez vite des callbacks. Ils s'exécutent sur le thread de la bibliothèque ; un traitement long y retarde la gestion du keep-alive. Confiez les messages à votre propre file ou à un worker.
- Abonnez-vous dans le callback connected. En clean session, le broker oublie les abonnements à la déconnexion ; se réabonner à cet endroit les restaure après chaque reconnexion automatique.
Compiler avec CMake
La bibliothèque installée fournit un paquet CMake : les projets plus importants n'ont pas besoin de flags d'édition de liens écrits à la main.
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)Passez aux cibles paho-mqtt3cs / paho-mqtt3as dès que vous ajoutez TLS.
TLS et identifiants
Passez le schéma de l'URI en ssl:// et le port en 8883, renseignez les options SSL et liez la variante TLS de la bibliothèque :
#define ADDRESS "ssl://broker.connectmq.com:8883"
MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
ssl_opts.enableServerCertAuth = 1;
ssl_opts.verify = 1; /* vérifier le nom d'hôte du certificat */
/* ssl_opts.trustStore = "ca.pem"; CA privée uniquement */
/* ssl_opts.keyStore = "client.pem"; certificat client (TLS mutuel) */
conn_opts.ssl = &ssl_opts;gcc pub_tls.c -o pub_tls -lpaho-mqtt3csSans trustStore, les emplacements de certificats par défaut d'OpenSSL sont utilisés, ce qui fonctionne avec les brokers dotés de certificats publiquement reconnus. L'API asynchrone possède les mêmes champs dans MQTTAsync_SSLOptions. Si le handshake échoue, écartez d'abord la chaîne de certificats avec les étapes de tester une connexion MQTT TLS.
MQTT 5 avec Paho C
MQTT 5 nécessite une option de création, un initialiseur v5 pour les options de connexion et les variantes ...5 des appels, qui renvoient des 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);La publication et l'abonnement fonctionnent de la même manière via MQTTClient_publishMessage5 et MQTTClient_subscribe5. Retrouvez n'importe quel reason code dans la référence des reason codes MQTT.
Les codes retour que vous verrez
| Code | Signification | Correctif habituel |
|---|---|---|
-1 (MQTTCLIENT_FAILURE) | Échec générique, souvent hôte injoignable ou handshake TLS raté | Vérifier hôte, port, pare-feu, certificats |
-10 (MQTTCLIENT_SSL_NOT_SUPPORTED) | URI ssl:// avec une bibliothèque sans TLS | Lier paho-mqtt3cs / paho-mqtt3as |
-14 (MQTTCLIENT_BAD_PROTOCOL) | Schéma d'URI inconnu | Utiliser tcp://, ssl://, ws:// ou wss:// |
4 | CONNACK : nom d'utilisateur ou mot de passe incorrect | Vérifier les identifiants |
5 | CONNACK : non autorisé | Vérifier les ACL du broker pour ce client |
Les codes positifs 1 à 5 sont les codes retour CONNACK de MQTT 3.1.1 du broker, expliqués en détail dans reason codes CONNACK. Affichez-les avec MQTTClient_strerror pour des logs lisibles.
Tester votre client
- Lancez
sub_async, puispub, et confirmez l'aller-retour ; recommencez avec le client en ligne d'un côté. - Coupez le réseau plus longtemps que le keep-alive puis rétablissez-le : vous devriez voir "connexion perdue", puis de nouveau "connecté" et "abonné" — la reconnexion automatique à l'œuvre. Les bonnes pratiques de reconnexion traitent du backoff pour des flottes entières.
- Donnez une mauvaise valeur à
MQTT_PASSet vérifiez que le callback d'échec le signale. - Exécutez sous Valgrind ou AddressSanitizer pendant quelques milliers de messages pour prouver que le callback de message libère tout.
Abonnez-vous au topic de la démo dans le navigateur
Paho C est léger, portable et utilisé en production depuis des années. Commencez par MQTTClient pour les outils et les tests, passez à MQTTAsync avec reconnexion automatique pour les services, liez les bibliothèques s quand vous ajoutez TLS et libérez chaque message reçu. Si vous avez besoin de C++ ou de Rust par-dessus, Paho C++ dans MQTT en C++ et paho-mqtt pour Rust encapsulent tous deux cette bibliothèque.
Questions fréquentes
Quelle différence entre MQTTClient et MQTTAsync dans Paho C ?
MQTTClient est l'API synchrone : des appels comme MQTTClient_connect bloquent jusqu'à leur fin, ce qui garde les programmes simples très courts. MQTTAsync ne bloque jamais ; chaque opération prend des callbacks de succès et d'échec, et l'API ajoute la reconnexion automatique et la mise en tampon hors ligne. Utilisez MQTTAsync pour les services de longue durée.
Quelle bibliothèque Paho C faut-il lier ?
paho-mqtt3c pour l'API synchrone sans TLS, paho-mqtt3cs pour le synchrone avec TLS, paho-mqtt3a pour l'asynchrone sans TLS et paho-mqtt3as pour l'asynchrone avec TLS. Lier une bibliothèque sans TLS et se connecter à une URI ssl:// échoue avec le code retour -10 (SSL non pris en charge).
Que signifie le code retour 5 de MQTTClient_connect ?
Les codes positifs de 1 à 5 sont les codes retour CONNACK de MQTT 3.1.1 envoyés par le broker. 5 signifie non autorisé et 4 nom d'utilisateur ou mot de passe incorrect. Les codes négatifs proviennent de la bibliothèque elle-même, par exemple -1 pour un échec générique comme un hôte injoignable.


