Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

venerdì 17 luglio 2026

MQTT
come usare MQTT in C - pt.3

Logan: Lo sai che sono tutte balle, vero? Sarà successo un quarto di questa roba e non così. E nel mondo reale le persone muoiono e nessun presuntuoso in calzamaglia del cazzo può evitarlo!

E siamo giunti, finalmente, alla terza e ultima puntata di "MQTT - come usare MQTT in C". E visto che la coerenza per me è molto importante, in questo articolo mostrerò come usare MQTT in Go (ah ah ah).

Beh, come ben sanno i miei lettori più assidui, io sono, fondamentalmente, un fanatico del C, però uso spesso (per lavoro) anche il Go e non disdegno di parlarne nei miei articoli; sarà, magari, perché è un bel linguaggio C-like, creato da due storici (e mitici) guru del mondo UNIX, Ken Thompson e Rob Pike, affiancati dal bravissimo Robert Griesemer (anche lui proveniente dal mondo UNIX). E non solo: il Go è la risposta di questi tre grandi informatici (e di Google) alle esagerate complicazioni introdotte nel C++11 (e non avevano ancora visto i successivi C++14/17/20/23!!!). In fondo all'articolo, per chi ha voglia di leggerlo, citerò uno scritto di Rob Pike sulla genesi del Go (che ho già citato qui e qui).

E la frase di Logan qui sopra? Beh, io direi che calza a pennello: nel mondo reale (della programmazione) bisogna fare scelte oculate (fregandosene delle mode) e il Go, per alcuni progetti, è una scelta oculata.

...meno balle: oggi usiamo il Go!...

Orbene, veniamo al dunque: ho scritto un programma mqttclient.go che è una replica delle due versioni in C pubblicate qui e qui. L'idea era che si potesse eseguire esattamente nello stesso modo e, ovviamente, produrre gli stessi risultati. Ok, vediamo un po se ci sono riuscito. Vai col codice!

// mqttclient.go - un semplice client mqtt in Go
package main

import (
"fmt"
"time"
"os"
mqtt "github.com/eclipse/paho.mqtt.golang"
)

// funzione main
func main() {

// test del numero di argomenti
argc := len(os.Args)
if (argc != 4) {
// errore: numero errato di argomenti
fmt.Printf("%s: numero errato di argomenti\n", os.Args[0])
fmt.Printf("uso: %s clientId topicPub topicSub (e.g.: %s client1 test/topic2 test/topic1)\n", os.Args[0], os.Args[0])
os.Exit(1)
}

// set delle opzioni di connessione
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("tcp://%s:%d", "localhost", 1883))
opts.SetClientID(os.Args[1])
opts.SetDefaultPublishHandler(onMessage)
opts.OnConnect = onConnect
opts.OnConnectionLost = onConnectionLost

// mi collego
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}

// eseguo subscribe e publish
subscribe(client)
publish(client)

// mi disconnetto
time.Sleep(2 * time.Second)
client.Disconnect(250)
}

// publish - funzione per pubblicare sui topic
func publish(client mqtt.Client) {

// pubblico messaggi sull'altro topic (topicPub in "uso: %s clientId topicPub topicSub")
for i := 0; i <= 2; i++ {
// attendo 2 secondi prima di pubblicare un messaggio
time.Sleep(2 * time.Second)

// pubblico
text := fmt.Sprintf("messaggio %d dal client %s", i, os.Args[1])
token := client.Publish(os.Args[2], 0, false, text)
token.Wait()
fmt.Printf("Messaggio %d publicato sul topic %s\n", i, os.Args[2]);
}
}

// subscribe - funzione per sottoscriversi ai topic
func subscribe(client mqtt.Client) {

// mi sottoscrivo a un topic (topicSub in "uso: %s clientId topicPub topicSub")
token := client.Subscribe(os.Args[3], 1, nil)
token.Wait()
fmt.Printf("%s: mi sottoscrivo al topic %s\n", os.Args[1], os.Args[3]);
}

// onMessage - una callback per i messaggi che arrivano sul topic sorvegliato
var onMessage mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {

fmt.Printf("messaggio ricevuto sul topic %s: \"%s\"\n", msg.Topic(), msg.Payload())
}

// onConnect - una callback per gestire la connessione
var onConnect mqtt.OnConnectHandler = func(client mqtt.Client) {

//fmt.Println("Connected")
}

// onConnectionLost - una callback per gestire la disconnessione
var onConnectionLost mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {

fmt.Printf("Connection lost: %v", err)
}

Come avrete notato, il codice è abbastanza semplice e super-commentato, quindi con c'è molto da spiegare, anche se per i lettori completamente a digiuno del Go non tutto sarà chiarissimo... ma questo non è un corso rapido di Go (ah ah ah). Vi do un consiglio spassionato: se vi interessa ampliare i vostri orizzonti e avere, anche, nuovi e interessanti sbocchi nel mondo del lavoro, studiatevi il Go! Si può cominciare con l'ottimo corso Go by Example, che è gratis ed è proposto e curato direttamente dal team ufficiale del Go (io ho cominciato, molti anni fa, proprio con questo corso e poi ho proseguito come autodidatta).

Chiusa la parentesi posso dire che, a parte eventuali incomprensioni da linguaggio, se fate un confronto con gli analoghi Client scritti in C dei precedenti articoli, noterete che il codice è veramente molto simile ed è moderatamente più compatto, visto che il Go è, praticamente, un C ad alto livello.

Faccio nuovamente notare che anche questo esempio si collega a un broker  locale (ossia installato sulla macchina stessa di esecuzione), quindi il collegamento è fatto con l’indirizzo: “tcp://localhost:1883”. Ma ci sono anche dei broker pubblici disponibili in rete che funzionano esattamente come quello locale. Nel mio caso (Linux) il broker  locale che uso è Mosquitto (disponibile anche per Windows) e funziona alla grande.

Ed è giunta l'ora di compilare ed eseguire. Qui, devo aggiungere qualche dettaglio: nelle versioni relativamente più recenti del Go (dalla versione 1.11) la compilazione dei programmi passa attraverso i Go Modules che sono il sistema standard ufficiale per la gestione delle dipendenze nei progetti Go. Questo semplifica un po' la gestione rispetto al precedente sistema (che non è il caso di descrivere qui). In parole povere: prima di compilare bisogna preparare un file che si chiama go.mod, che normalmente sta nella stessa directory dove ci sono i sorgenti. Per crearlo ho scritto un piccolo script-shell (buildmod.sh) per facilitarmi (e facilitarvi) la vita: eccolo!

#!/bin/bash
rm go.mod
rm go.sum
rm mqttclient
go mod init mqttclient
go mod tidy
lanciando lo script vedrete questo:
aldo@Linux $ ./buildmod.sh
go: creating new go.mod: module mqttclient
go: to add module requirements and sums:
go mod tidy
go: finding module for package github.com/eclipse/paho.mqtt.golang
go: found github.com/eclipse/paho.mqtt.golang in github.com/eclipse/paho.mqtt.golang v1.5.1

una volta creato il go.mod (solo per la prima compilazione, non serve ricrearlo ogni volta) si può compilare ed eseguire. Compiliamo (e questo è veramente semplicissimo):

aldo@Linux $ go build

ed eseguiamo in due terminali diversi (possibilmente in quasi-contemporanea, per non perdersi messaggi: se siete troppo lenti, quando il secondo client parte il primo potrebbe già aver pubblicato tutti i suoi messaggi, ah ah ah. Comunque le "strane" sleep che ho aggiunto sono proprio per rendere leggibili i risultati). Il risultato è questo:

TERMINALE 1:

aldo@Linux $ ./mqttclient Client2 test/topic1 test/topic2
Client2: mi sottoscrivo al topic test/topic2
messaggio ricevuto sul topic test/topic2: "messaggio 0 dal client Client1"
Messaggio 0 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 1 dal client Client1"
Messaggio 1 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 2 dal client Client1"
Messaggio 2 publicato sul topic test/topic1

TERMINALE 2:

aldo@Linux $ ./mqttclient Client1 test/topic2 test/topic1
Client1: mi sottoscrivo al topic test/topic1
Messaggio 0 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 0 dal client Client2"
Messaggio 1 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 1 dal client Client2"
Messaggio 2 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 2 dal client Client2"

Visto? Anche questa versione in Go funziona perfettamente (e identicamente) alle altre scritte in C!

Ok, per oggi può bastare. Nel prossimo articolo non so ancora di cosa parlerò, ma vi prometto che sarà, come sempre, molto interessante! Ah, come promesso all'inizio dell'articolo, dopo il solito saluto vi proporrò una citazione di Rob Pike sulla genesi del Go. Leggetevi la citazione (e magari anche l'intero articolo di Pike) è molto interessante!

Ciao, e al prossimo post!

"...Intorno a settembre 2007, stavo lavorando a una modifica minore ma fondamentale su un enorme programma C++ di Google, uno con cui tutti voi avete avuto a che fare, e le mie compilazioni impiegavano circa 45 minuti sul nostro gigantesco cluster di compilazione distribuito. Fu annunciato che ci sarebbe stata una presentazione tenuta da un paio di dipendenti di Google membri del comitato per gli standard C++. Avrebbero illustrato le novità in arrivo con il C++0x, come veniva chiamato all’epoca. (Oggi è noto come C++11).

Nel corso di quell’ora di intervento abbiamo sentito parlare di circa 35 nuove funzionalità che erano in fase di progettazione. In realtà ce n’erano molte di più, ma solo 35 sono state descritte durante l’intervento. Alcune delle funzionalità erano minori, ovviamente, ma quelle citate nella presentazione erano almeno abbastanza significative da meritare di essere menzionate. Alcune erano molto sottili e difficili da comprendere, come i riferimenti rvalue, mentre altre sono particolarmente tipiche del C++, come i modelli variadici, e altre ancora sono semplicemente folli, come i letterali definiti dall’utente.

A questo punto mi sono posto una domanda: il comitato del C++ credeva davvero che il problema del C++ fosse che non avesse abbastanza funzionalità? Sicuramente, in una variante della battuta di Ron Hardin, sarebbe un risultato maggiore semplificare il linguaggio piuttosto che arricchirlo. Ovviamente è ridicolo, ma tenete a mente l’idea...” [da “Less is exponentially more”, Rob Pike, 2012]

martedì 30 giugno 2026

MQTT
come usare MQTT in C - pt.2

Logan: Dove vado non c'è redenzione. E chi la vuole?

Dove eravamo rimasti? Ah, si, si parlava di MQTT (Message Queuing Telemetry Transport), un bel protocollo di comunicazione da studiare e usare. Nello scorso articolo abbiamo cominciato realizzando una implementazione di un semplice Client MQTT usando la libpaho, che è la libreria più usata e (probabilmente) più performante, ma non è l'unica: e quindi, come promesso, in questo secondo articolo mostrerò un altro semplice esempio di Client, questa volta usando la libreria MQTT-C. Ah, il film della frase qui sopra è lo stesso dell'altra volta, lo splendido Logan del bravo James Mangold. E, direi, sono d'accordo con il buon Wolverine/H.Jackman: anche noi programmatori C non abbiamo bisogno di redenzione... o forse si? Beh, chi usa Linux sicuramente no, mentre gli altri... ah ah ah.

...non ho bisogno di redenzione, ho bisogno di un MQTT funzionante!...

Come già anticipato nello scorso articolo, MQTT-C è una libreria semplice e leggerissima, compatibile solo con lo standard MQTT 3.1.1. È ideale per applicazioni embedded (con OS o anche di tipo "bare-metal") ma usabile anche su Linux o alcuni RTOS. La libreria consiste in solo due file sorgente (mqtt.c e mqtt_pal.c) da includere nel progetto e non dipende da librerie di sistema specifiche, rendendola ideale per ambienti con risorse limitate. L'interfaccia di programmazione è relativamente semplice: si può inizializzare un Client, connettersi a un broker, pubblicare messaggi e sottoscriversi ai "topic" con poche chiamate dirette.

Per l'uso, è necessario includere l'header mqtt.h (e, indiretamente, l'altro header mqtt_pal.h) e istanziare un oggetto mqtt_client. La ben più complessa Eclipse Paho MQTT C Client Library offre API sincrone e asincrone con supporto nativo per SSL/TLS e threading, mentre la semplice MQTT-C delega la gestione della rete e dell'I/O alle risorse del sistema ospite (Linux, un RTOS embedded o addirittura un "bare-metal"), mantenendo lo stack MQTT dedicato e leggero.

Il Client di esempio che ho scritto oggi è, più o meno, come quello dello scorso articolo: implementa un client che funziona, allo stesso tempo, come Publisher e come Subscriber, usando opportunamente la linea di comando: in questo modo si possono, ad esempio, lanciare due client che scrivono/leggono sui rispettivi topic "incrociati" (e cioè: il client1  pubblica sul topic2  e legge dal topic1  e il client2  pubblica sul topic1  e legge dal topic2)... spero di essere stato chiaro. Comunque, vediamo il codice, compiliamo ed eseguiamo come appena descritto, e sarà tutto più chiaro. Vai col codice!

// mqttclient.c - un semplice client mqtt (con MQTT-C)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "include/mqtt.h"
#include "templates/posix_sockets.h"

// prototipi locali
void onMessage(void** unused, struct mqtt_response_publish *published);
void* clientDaemon(void* client);
void cleanExit(int status, int sockfd, pthread_t *client_daemon);

// funzione main
int main(int argc, char *argv[])
{
// test del numero di argomenti
if (argc != 4) {
// errore: numero errato di argomenti
printf("%s: numero errato di argomenti\n", argv[0]);
printf("uso: %s clientId topicPub topicSub"
"(e.g.: %s client1 test/topic2 test/topic1)\n", argv[0], argv[0]);
return EXIT_FAILURE;
}

// apro il non-blocking TCP socket collegato al broker
int sockfd;
if ((sockfd = open_nb_socket("localhost", "1883")) == -1) {
printf("non posso aprire il socket\n");
cleanExit(EXIT_FAILURE, sockfd, NULL);
}

// creo un client
struct mqtt_client client; // dimensionare i buffer in base ai messaggi previsti in uso
uint8_t sendbuf[2048];
uint8_t recvbuf[1024];
mqtt_init(&client, sockfd, sendbuf, sizeof(sendbuf),
recvbuf, sizeof(recvbuf), onMessage);

// crea una sessione anonima/vuota e invio una richiesta di connessione al broker
const char* client_id = NULL;
uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION;
mqtt_connect(&client, client_id, NULL, NULL, 0, NULL, NULL, connect_flags, 400);

// check errori
if (client.error != MQTT_OK) {
fprintf(stderr, "error: %s\n", mqtt_error_str(client.error));
cleanExit(EXIT_FAILURE, sockfd, NULL);
}

// avvio un thread per aggiornare il client (gestisce il traffico I/O del client)
pthread_t client_daemon;
if(pthread_create(&client_daemon, NULL, clientDaemon, &client)) {
printf("non posso avviare il client daemon\n");
cleanExit(EXIT_FAILURE, sockfd, NULL);
}

// mi sottoscrivo a un topic (topicSub in "uso: %s clientId topicPub topicSub")
printf("%s: mi sottoscrivo al topic %s\n", argv[1], argv[3]);
mqtt_subscribe(&client, argv[3], 0);

// pubblico messaggi sull'altro topic (topicPub in "uso: %s clientId topicPub topicSub")
for (int i = 0; i < 3; i++) {
// attendo 2 secondi prima di pubblicare un messaggio
sleep(2);

// pubblico
char payload[256];
snprintf(payload, sizeof(payload), "messaggio %d dal client %s", i, argv[1]);
mqtt_publish(&client, argv[2], payload, strlen(payload) + 1, MQTT_PUBLISH_QOS_0);

// check errori
if (client.error != MQTT_OK) {
fprintf(stderr, "error: %s\n", mqtt_error_str(client.error));
cleanExit(EXIT_FAILURE, sockfd, &client_daemon);
}

printf("Messaggio %d publicato sul topic %s\n", i, argv[2]);
}

// mi disconnetto
sleep(2);
cleanExit(EXIT_SUCCESS, sockfd, &client_daemon);
}

// onMessage - una callback per i messaggi che arrivano sul topic sorvegliato
void onMessage(void** unused, struct mqtt_response_publish *published)
{
char *topic_name = malloc(published->topic_name_size + 1);
memcpy(topic_name, published->topic_name, published->topic_name_size);
topic_name[published->topic_name_size] = '\0';
printf("messaggio ricevuto sul topic %s: \"%s\"\n",
topic_name, (const char*)published->application_message);
free(topic_name);
}

// clientDaemon - un thread che sincronizza l'I/O mqtt
void* clientDaemon(void* client)
{
// loop infinito del thread
for (;;) {
// sincronizzo
mqtt_sync((struct mqtt_client*) client);

// thread sleep (NOTA: in produzione NON usare usleep(3). Usare nanosleep(2))
usleep(100000U);
}

return NULL;
}

// cleanExit - uscita controllata
void cleanExit(int status, int sockfd, pthread_t *client_daemon)
{
// chiudo il socket
if (sockfd != -1)
close(sockfd);

// cancello il thread (NOTA: in produzione NON usare pthread_cancel(3) ma uno stop-flag)
if (client_daemon != NULL)
pthread_cancel(*client_daemon);

// chiudo l'applicazione
exit(status);
}

Come avrete notato, il codice è abbastanza semplice e super-commentato, quindi con c'è molto più da spiegare. Se fate un confronto con l'analogo Client scritto per la libpaho noterete che il codice è veramente molto simile. Faccio solo notare che anche questo esempio si collega a un broker locale (ossia installato sulla macchina stessa di esecuzione), quindi il collegamento è fatto con l'indirizzo: "tcp://localhost:1883". Ma ci sono anche dei broker pubblici disponibili in rete che funzionano esattamente come quello locale. Nel mio caso (Linux) il broker  locale che uso è Mosquitto (disponibile anche per Windows) e funziona alla grande.

L'esempio che ho scritto è ispirato dagli esempi contenuti nella pagina Github di MQTT-C, ho fatto un po' di cambi ma l'ispirazione proviene da lì. Per cui faccio anche notare, come da apposite NOTE che ho aggiunto al codice, che sono presenti alcune cose da cambiare per una eventuale versione di produzione di questo Client. Le NOTE sono:

  • NOTA: in produzione NON usare usleep(3). Usare nanosleep(2): come già visto in un mio vecchio articolo, usleep(3) è deprecata da tempo, e bisogna usare nanosleep(2), anche se è probabile (ma non sicuro) che la usleep(3) della libc sia implementata internamente usando nanosleep(2).
  • NOTA: in produzione NON usare pthread_cancel(3) ma uno stop-flag): come già visto in un mio vecchio articolo, pthread_cancel(3) è una funzione molto problematica di cui sconsiglio l'uso: meglio passare uno stop-flag al thread per forzarne una uscita pulita.

Ed è giunta l'ora di compilare ed eseguire. Compiliamo:

aldo@Linux $ gcc -o mqttclient mqttclient.c mqtt_pal.c mqtt.c

ed eseguiamo in due terminali diversi (possibilmente in quasi-contemporanea, per non perdersi messaggi: se siete troppo lenti, quando il secondo client parte il primo potrebbe già aver pubblicato tutti i suoi messaggi, ah ah ah. Comunque le "strane" sleep(3) che ho aggiunto sono proprio per rendere leggibili i risultati). Il risultato è questo:

terminale 1:

aldo@Linux $ ./mqttclient Client2 test/topic1 test/topic2
Client2: mi sottoscrivo al topic test/topic2
messaggio ricevuto sul topic test/topic2: "messaggio 0 dal client Client1"
Messaggio 0 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 1 dal client Client1"
Messaggio 1 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 2 dal client Client1"
Messaggio 2 publicato sul topic test/topic1

terminale 2:

aldo@Linux $ ./mqttclient Client1 test/topic2 test/topic1
Client1: mi sottoscrivo al topic test/topic1
Messaggio 0 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 0 dal client Client2"
Messaggio 1 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 1 dal client Client2"
Messaggio 2 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 2 dal client Client2"

Visto? Anche questo funziona perfettamente!

Ok, per oggi può bastare. Nel prossimo articolo proveremo (udite! udite!) a cambiare linguaggio e implementeremo un Client MQTT in Go: non è la prima volta che affrontiamo il gran linguaggio Go su queste pagine eh! Non dovrebbe sorprendervi! E, come già vi ho raccomandato l'altra volta, non trattenete il respiro nell'attesa! (può nuocere gravemente alla salute).

Ciao, e al prossimo post!

domenica 31 maggio 2026

MQTT
come usare MQTT in C - pt.1

Charles Xavier: Due giorni in viaggio, un solo pasto e non abbiamo chiuso occhio. Lei ha 11 anni e io 90, cazzo!
Logan: Sono 101 ragioni per continuare a muoverci.

MQTT, un titolo secco di una sola parola. E qui ci vuole un film con un titolo (e un contenuto) altrettanto secco: Logan del bravo James Mangold fa proprio al caso nostro. Un film "crepuscolare" che celebra la fine della Saga dei Mutanti, con un Wolverine/H.Jackman e un Prof.X/P.Stewart veramente perfetti. Un film super-raccomandato con frasi come quella sopra che ci introducono al tema di oggi: dopo tanta fatica fatta per studiare e assimilare gli n-mila protocolli di rete disponibili, è il caso di imparare a usarne un'altro? E la risposta è si: in questo articolo parleremo di MQTT!

...facciamo in fretta, se no ci perdiamo il prossimo MQTT!...

Ok, veniamo al dunque: cosa è, esattamente, MQTT (Message Queuing Telemetry Transport)? È un protocollo di messaggistica leggero, basato su standard aperti, di tipo "pubblica/sottoscrivi", progettato per garantire una comunicazione efficiente tra dispositivi in reti con larghezza di banda ridotta, elevata latenza o inaffidabili. È un protocollo relativamente recente: fu sviluppato nel 1999 per il monitoring di oleodotti via satellite, e in seguito, visto che funzionava bene, è diventato lo standard de facto per le applicazioni dell'Internet delle cose (IoT) e dell'IoT industriale (IIoT) grazie al suo impatto minimo sulle risorse e alla sua scalabilità.

Il protocollo lavora usando un'architettura basata su un broker centrale (una specie di server) in cui i dispositivi (client publisher) pubblicano messaggi su argomenti specifici anziché inviare dati direttamente ad altri dispositivi. Gli abbonati (client subscriber) registrano il proprio interesse per questi argomenti, consentendo al broker di filtrare e distribuire i messaggi solo ai destinatari pertinenti. Le caratteristiche principali includono tre livelli di Quality of Service (QoS) per garantire la consegna, messaggi conservati per gli aggiornamenti di stato e supporto per la crittografia e l’autenticazione TLS per garantire la sicurezza. La sua natura binaria e le intestazioni dei messaggi di piccole dimensioni (fino a due byte) lo rendono ideale per microcontrollori con risorse limitate e sensori sensibili al consumo energetico.

Si può anche notare che per pubblicare e sottoscrivere si usa una nomenclatura gerarchica molto simile a quella di un file-system POSIX, quindi con alberi di nomi separati dal carattere "/", e questi alberi possono essere molto nidificati, esattamente (come appena detto) come in un file-system: un'idea molto interessante e azzeccata. Notare che nella terminologia MQTT questi pseudo-pathname, usati per pubblicare e sottoscrivere, vengono chiamati topic.

E non possiamo continuare senza mostrare un po' di codice, no? Ok, per scrivere il codice dobbiamo/possiamo appoggiarci a qualche libreria di quelle disponibili: per il nostro amato C abbiamo due scelte principali:

  • Eclipse Paho MQTT C Client library: è una libreria abbastanza sofisticata e completa, compatibile con i due standard disponibili, MQTT 3.1.1 e MQTT 5.0. È ideale per applicazioni "senza compromessi", su Hardware di buon livello (PC e Server).
  • MQTT-C: è una libreria semplice e leggerissima, compatibile solo con lo standard MQTT 3.1.1. È ideale per applicazioni embedded (con OS o anche di tipo "bare-metal") ma usabile anche su Linux o alcuni RTOS.

Per questo articolo ho scritto un semplice esempio che usa la libpaho (versione 3.1.1), ed è un codice che implementa un client che funziona, allo stesso tempo, come Publisher e come Subscriber, usando opportunamente la linea di comando: in questo modo si possono, ad esempio, lanciare due client che scrivono/leggono sui rispettivi topic "incrociati" (e cioè: il client1  pubblica sul topic2  e legge dal topic1  e il client2  pubblica sul topic1  e legge dal topic2)... spero di essere stato chiaro. Comunque, vediamo il codice, compiliamo ed eseguiamo come appena descritto, e sarà tutto più chiaro. Vai col codice!

// mqttclient.c - un semplice client mqtt (con libpaho)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "MQTTClient.h"

#define QOS 1
#define TIMEOUT 10000L

// prototipi locali
int onMessage(void *context, char *topicName, int topicLen, MQTTClient_message *message);

// funzione main
int main(int argc, char *argv[])
{
// test del numero di argomenti
if (argc != 4) {
// errore: numero errato di argomenti
printf("%s: numero errato di argomenti\n", argv[0]);
printf("uso: %s clientId topicPub topicSub"
"(e.g.: %s client1 test/topic2 test/topic1)\n", argv[0], argv[0]);
return EXIT_FAILURE;
}

// creo un client
MQTTClient client;
MQTTClient_create(&client, "tcp://localhost:1883",
argv[1], MQTTCLIENT_PERSISTENCE_NONE, NULL);

// set delle callback: in questo caso solo la onMessage()
MQTTClient_setCallbacks(client, NULL, NULL, onMessage, NULL);

// set delle opzioni di connessione
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;

// mi collego
int rc;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) {
printf("errore di connessione: codice %d\n", rc);
return EXIT_FAILURE;
}

// mi sottoscrivo a un topic (topicSub in "uso: %s clientId topicPub topicSub")
printf("%s: mi sottoscrivo al topic %s\n", argv[1], argv[3]);
MQTTClient_subscribe(client, argv[3], QOS);

// pubblico messaggi sull'altro topic (topicPub in "uso: %s clientId topicPub topicSub")
MQTTClient_message pubmsg = MQTTClient_message_initializer;
for (int i = 0; i < 3; i++) {
// attendo 2 secondi prima di pubblicare un messaggio
sleep(2);

// pubblico
char payload[256];
snprintf(payload, sizeof(payload), "messaggio %d dal client %s", i, argv[1]);
pubmsg.payload = (void*)payload;
pubmsg.payloadlen = strlen(payload);
pubmsg.qos = QOS;
pubmsg.retained = 0;
MQTTClient_deliveryToken token;
MQTTClient_publishMessage(client, argv[2], &pubmsg, &token);
MQTTClient_waitForCompletion(client, token, TIMEOUT);
printf("Messaggio %d publicato sul topic %s\n", i, argv[2]);
}

// mi disconnetto
sleep(2);
MQTTClient_disconnect(client, TIMEOUT);
MQTTClient_destroy(&client);
return EXIT_SUCCESS;
}

// onMessage - una callback per i messaggi che arrivano sul topic sorvegliato
int onMessage(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
printf("messaggio ricevuto sul topic %s: \"%.*s\"\n",
topicName, message->payloadlen, (char *)message->payload);
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}

Come avrete notato, il codice è abbastanza semplice e super-commentato, quindi con c'è molto più da spiegare. Faccio solo notare che questo esempio si collega a un broker  locale (ossia installato sulla macchina stessa di esecuzione), quindi il collegamento è fatto con l'indirizzo: "tcp://localhost:1883". Ma ci sono anche dei broker pubblici disponibili in rete che funzionano esattamente come quello locale. Nel mio caso (Linux) il broker  locale che uso è Mosquitto (disponibile anche per Windows) e funziona alla grande.

E, come detto sopra, compiliamo:

aldo@Linux $ gcc -o mqttclient mqttclient.c -lpaho-mqtt3c

ed eseguiamo in due terminali diversi (possibilmente in quasi-contemporanea, per non perdersi messaggi: se siete troppo lenti, quando il secondo client parte il primo potrebbe già aver pubblicato tutti i suoi messaggi, ah ah ah. Comunque le "strane" sleep(3) che ho aggiunto sono proprio per rendere leggibili i risultati). Il risultato è questo:

terminale 1:

aldo@Linux $ ./mqttclient Client2 test/topic1 test/topic2
Client2: mi sottoscrivo al topic test/topic2
Messaggio 0 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 0 dal client Client1"
Messaggio 1 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 1 dal client Client1"
Messaggio 2 publicato sul topic test/topic1
messaggio ricevuto sul topic test/topic2: "messaggio 2 dal client Client1"

terminale 2:

aldo@Linux $ ./mqttclient Client1 test/topic2 test/topic1
Client1: mi sottoscrivo al topic test/topic1
messaggio ricevuto sul topic test/topic1: "messaggio 0 dal client Client2"
Messaggio 0 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 1 dal client Client2"
Messaggio 1 publicato sul topic test/topic2
messaggio ricevuto sul topic test/topic1: "messaggio 2 dal client Client2"
Messaggio 2 publicato sul topic test/topic2

Visto? Funziona perfettamente!

Ok, per oggi può bastare. Nel prossimo articolo proveremo a usare la leggerissima libreria MQTT-C. E, come spesso vi raccomando, non trattenete il respiro nell'attesa! (può nuocere gravemente alla salute).

Ciao, e al prossimo post!