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.

lunedì 14 settembre 2026

The FileCopy
come scrivere una funzione di File Copy in C (e Go) - pt.2

Childs: Beh... che facciamo?
R.J.MacReady: Perché... non aspettiamo qui ancora un po'... e vediamo che succede?

(...una premessa: questo post (parte 2 di 3) è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)

Ok, riprendiamo da dove ci eravamo lasciati nell'ultimo articolo (l'avete letto vero?) e, come promesso, questa volta l'argomento sarà una versione con buffered I/O  della funzione che esegue la copia di un file (una FileCopy, per gli amici). Ma è doveroso, prima, fornire un altra immagine tratta da The Thing del Maestro John Carpenter , se no potrebbe sembrare che questo sia solo un blog di programmazione, mentre, come ben sapete, è un blog per programmatori cinefili...

...con un cappello così si programma meglio...

Allora, ripetiamo: in questa nuova version useremo I/O bufferizzato (e quindi, per esempio, fread(3) invece di read(2)) perché l'obbiettivo questa volta è la portabilità, e cosa c'è di più portabile (nel C) che usare il contenuto di stdio.h? Il codice che vedremo tra un attimo usa lo stesso main() della versione dello scorso articolo e gli unici cambi sono interni alla funzione cpFile(). In realtà, anche la cpFile() è quasi identica da un punto di vista logico, visto che le funzioni buffered hanno una sintassi d'uso e un funzionamento molto simile alle equivalenti versioni unbuffered. Ecco, posso aggiungere che se la versione buffered viene molto diversa (esteticamente e logicamente) dalla unbuffered c'è qualcosa che non va... ma su questo punto farò una breve digressione alla fine del post. Per il momento: vai col codice!

// cpbuffered.c - un cp(1) con buffered I/O
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// prototipi locali
off_t cpFile(const char* src, const char* dest);

// funzione main()
int main(int argc, char *argv[])
{
// test argomenti
if (argc != 3) {
// errore: numero errato di argomenti: esco
fprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);
fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);
return EXIT_FAILURE;
}

// eseguo la copia
off_t n_copied;
if ((n_copied = cpFile(argv[2], argv[1])) == -1) {
// errore: copia fallita: esco
fprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));
exit(EXIT_FAILURE);
}

// esco con Ok
printf("%s: copiati %ld byte\n", argv[0], n_copied);
return EXIT_SUCCESS;
}

// cpFile() - copia il file <src> nel file <dest>
off_t cpFile(const char *dest, const char *src)
{
// apro il file sorgente
FILE *fp_in;
if ((fp_in = fopen(src, "rb")) == NULL) {
// ritorno errore
return -1;
}

// apro il file destinazione
FILE *fp_out;
if ((fp_out = fopen(dest, "wb")) == NULL) {
// errore: salvo errno della fopen(3) precedente e chiudo il file
int save_errno = errno;
fclose(fp_in);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// r/w loop per la copia usando buffered I/O
off_t n_copied = 0;
size_t n_read;
char buffer[BUFSIZ];
while ((n_read = fread(buffer, 1, sizeof(buffer), fp_in)) > 0) {
// write buffer (con reset prudenziale di errno)
errno = 0;
if (fwrite(buffer, 1, n_read, fp_out) < n_read) {
// errore: salvo errno della fwrite(3) precedente e chiudo i file
// NOTA: se errno==0 con scrittura parziale: il disco è pieno (ENOSPC)
int save_errno = (errno != 0) ? errno : ENOSPC;
fclose(fp_in);
fclose(fp_out);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// incremento il contatore di byte copiati
n_copied += n_read;
}

// r/w loop concluso: check finale errori di lettura con ferror(3)
if (ferror(fp_in)) {
// errore di lettura prima del EOF: salvo errno e chiudo i file
int save_errno = errno;
fclose(fp_in);
fclose(fp_out);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// raggiunto EOF con successo: check fclose(3) in scrittura per i buffer pendenti
if (fclose(fp_out) != 0) {
// errore di chiusura fp_out: salvo errno e chiudo anche fp_in
int save_errno = errno;
fclose(fp_in);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// chiudo fp_in e ritorno i byte copiati
fclose(fp_in);
return n_copied;
}

Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale. Il main(), come anticipato, è identico a quello visto precedentemente, mentre nella cpFile() si ripetono, esattamente, le spesse operazioni della versione unbuffered, usando però fopen(3) invece di open(2), fclose(3) invece di close(2), ecc. E dove troviamo qualche differenza significativa? Solo nella parte finale, quella dopo il loop while di lettura/scrittura: in questa versione vengono fatti alcuni test sui descrittori di file fp_in e fp_out per verificare che tutto il procedimento sia andato a buon fine: nella versione unbuffered questi test erano impliciti nelle operazioni di read/write (testando se il risultato era uguale a -1), mentre che in questo caso bisogna usare una funzione a parte, ferror(3), e questo perché:

On success, fread() and fwrite() return the number of items read or
written. This number equals the number of bytes transferred only when
size is 1. If an error occurs, or the end of the file is reached, the
return value is a short item count (or zero).
...
fread() does not distinguish between end-of-file and error, and callers
must use feof(3) and ferror(3) to determine which occurred.

Quello sopra è quello che riporta la man-page di fread(3) e fwrite(3), e credo che sia una giustificazione sufficiente sul perché ho scritto il codice così. Quindi, come anticipato, le versioni buffered e unbuffered devono essere quasi sovrapponibili (a parte i test finali), e mi sembra che sia esattamente il risultato raggiunto.

Ed ora la digressione promessa: mi è capitato di trovare in rete (anche in pregevoli blog/siti di programmazione) esempi di implementazione di lettura di file con buffered I/O che usano dei loop di questo tipo:

// loop di lettura/scrittura
while (!feof(fp_in)) {
// legge e scrive un buffer
...
}

ecco, come si può commentare questo? Con una sola parola:

NO

Se uno scrive il loop in questa maniera vuol dire che non ha letto la man-page di fread(3) e fwrite(3) o l'ha letta e non ne ha capito il contenuto. Non c'è bisogno di reinventare la ruota, ripeto: fread(3) e fwrite(3) funzionano quasi nello stesso modo di read(2) e write(2), quindi, se l'esempio della cpFile() unbuffered del post precedente era buono (e lo era!), allora l'esempio del post attuale deve risultare (quasi) uguale. Il loop con un while() che testa feof(3) è corretto sintatticamente ma non lo è logicamente, perché inizia testando qualcosa che non è ancora disponibile (uhm, un test predittivo?) e che, oltretutto, non serve testare. Bah, non voglio dilungarmi ulteriormente e vi rimando alla ottima analisi contenuta qui (nel sempre ottimo stackoverflow.com).

Ovviamente spero di non aver offeso nessuno (con la digressione precedente): ricordate, errare humanum est... e, sicuramente, anche in questo blog avrò scritto in passato qualche scemata (spero non grave come quella appena mostrata). Vi assicuro, però, che sto sempre attentissimo a non proporre soluzioni che non ho avuto tempo di scrivere e provare in maniera approfondita, se no invece di un blog di programmazione artistica questo sarebbe un blog di programmazione alla speriamo che funziona...

Ok, per questa seconda puntata quanto detto può bastare. Nella prossima, come promesso nel titolo stesso, vedremo come fare un FileCopy in Go. E sarà interessante, ve lo prometto!

Ciao, e al prossimo post!

domenica 23 agosto 2026

The FileCopy
come scrivere una funzione di File Copy in C (e Go) - pt.1

Dr.Blair: Non so di chi fidarmi.
R.J.MacReady: Ti capisco benissimo, Blair. La fiducia è una cosa molto rara oggigiorno. Sai che cosa ti dico? Affidati solo al Signore.

(...una premessa: questo post (parte 1 di 3) è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)

Ok, questo post non centra niente con The Thing, il Capolavoro Immortale del Maestro John Carpenter (a parte la piccola assonanza del nome). Anzi, è solo una scusa per celebrarlo, dato che l'ho rivisto (per la millesima volta) da poco. Comunque, dato che ci siamo, parleremo anche un po' di C...

...Capolavoro Immortale...
Con questo post vedremo come scrivere una funzione che esegue la copia di un file (una FileCopy, per gli amici), dato che i sistemi POSIX (come UNIX e Linux) non prevedono una funzione specifica di libreria per farlo. Ci sono, evidentemente, mille maniere per scriverla, e questa volta ne ho pensate due. Vai con la prima!
// cpunbuffered.c - un cp(1) con unbuffered I/O
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

// prototipi locali
off_t cpFile(const char* src, const char* dest);

// funzione main()
int main(int argc, char *argv[])
{
// test argomenti
if (argc != 3) {
// errore: numero errato di argomenti: esco
fprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);
fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);
return EXIT_FAILURE;
}

// eseguo la copia
off_t n_copied;
if ((n_copied = cpFile(argv[2], argv[1])) == -1) {
// errore: copia fallita: esco
fprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));
exit(EXIT_FAILURE);
}

// esco con Ok
printf("%s: copiati %ld byte\n", argv[0], n_copied);
return EXIT_SUCCESS;
}

// cpFile() - copia il file <src> nel file <dest>
off_t cpFile(const char *dest, const char *src)
{
// apro il file sorgente
int fd_in;
if ((fd_in = open(src, O_RDONLY)) == -1) {
// ritorno errore
return -1;
}

// apro il file destinazione
int fd_out;
if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {
// errore: salvo errno della open(2) precedente e chiudo il file
int save_errno = errno;
close(fd_in);

// set errno salvato (evitando quello della close(2)) e ritorno errore
errno = save_errno;
return -1;
}

// r/w loop per la copia usando unbuffered I/O
off_t n_copied = 0;
ssize_t n_read;
char buffer[BUFSIZ];
while ((n_read = read(fd_in, buffer, sizeof(buffer))) > 0) {
// write buffer
if (write(fd_out, buffer, n_read) == -1) {
// errore: salvo errno della write(2) precedente e chiudo i file
int save_errno = errno;
close(fd_in);
close(fd_out);

// set errno salvato (evitando quello della close(2)) e ritorno errore
errno = save_errno;
return -1;
}

// incremento il contatore di bytes copiati
n_copied += n_read;
}

// chiudo i file
close(fd_in);
close(fd_out);

// ritorno il numero di bytes copiati
return n_copied;
}

Ok, come vedete è, volutamente, super-commentato, e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale. Il main(), in questo caso, serve solo per testare la funzione di copia, e il programma generato si comporta (a livello basico) come il comando UNIX/Linux cp(1), che è proprio quello che vogliamo emulare usando la nostra nuova funzione di libreria.

La funzione che esegue il lavoro l'ho chiamata cpFile() ed è abbastanza semplice, come avrete notato. Usa l'I/O non bufferizzato (quindi, per esempio, read(2) invece di fread(3)) e, pur essendo compattissima ed efficiente, tratta anche in maniera esaustiva gli errori ed è scritta per essere una funzione di libreria, quindi non scrive nulla su stderr e stdout ma si limita a eseguire il lavoro e a restituire un codice di ritorno che sarà il numero dei byte copiati o -1 in caso di errore. Il risultato può essere trattato dal chiamante (in questo caso il main()) per visualizzare eventuali errori usando strerror(3) ed errno(3). Tutto il lavoro viene eseguito in un loop che legge un buffer dal file sorgente e lo scrive nel file destinazione, fino alla fine del file. Il resto del codice è apertura/chiusura dei file e trattamento degli errori. Visto che usiamo l'unbuffered I/O ho dimensionato il buffer di lettura/scrittura usando la define BUFSIZ del sistema che dovrebbe garantire la dimensione ottimale per le operazioni di I/O.

Posso aggiungere che la nostra funziona cpFile() usa, evidentemente, una interfaccia GNU-specific (come già detto sopra: ritorna -1 in caso di errore), e il valore di errno viene opportunamente salvato per permettere al chiamante di verificare il problema reale che ha creato l'errore nella funzione.

Volendo si potrebbe modificare l'interfaccia per usarne una di tipo XSI-compliant (ossia: POSIX): in questo caso si dovrebbe ritornare un codice di successo (che normalmente è 0) o di errore (un numero positivo: l'errno) per permettere al chiamante le stesse attività appena viste sopra. Faccio notare, infine, che anche se l'interfaccia XSI-compliant canonica dovrebbe ritornare l'errno gestito dalla libc, è possibile, per funzioni particolari, usare degli errori user-defined che potremo, ad esempio, definire in un header apposito del progetto, ma in quel caso non potremo, ovviamente, usare la strerror(3) del sistema, ma dovremo scriverne una ad-hoc per i nostri nuovi errori.

Avevo detto che avrei proposto due versioni: fermo restando il main() (che va bene per entrambi i casi) la versione alternativa è questa:

// cpspecial.c - un cp(1) con sendfile(2)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/sendfile.h>

// prototipi locali
off_t cpFile(const char* src, const char* dest);

// funzione main()
int main(int argc, char *argv[])
{
// test argomenti
if (argc != 3) {
// errore: numero errato di argomenti: esco
fprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);
fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);
return EXIT_FAILURE;
}

// eseguo la copia
off_t n_copied;
if ((n_copied = cpFile(argv[2], argv[1])) == -1) {
// errore: copia fallita: esco
fprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));
exit(EXIT_FAILURE);
}

// esco con Ok
printf("%s: copiati %ld byte\n", argv[0], n_copied);
return EXIT_SUCCESS;
}

// cpFile() - copia il file <src> nel file <dest>
off_t cpFile(const char *dest, const char *src)
{
// apro il file sorgente
int fd_in;
if ((fd_in = open(src, O_RDONLY)) == -1) {
// ritorno errore
return -1;
}

// apro il file destinazione
int fd_out;
if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {
// errore: salvo errno della open(2) precedente e chiudo il file
int save_errno = errno;
close(fd_in);

// set errno salvato (evitando quello della close(2)) e ritorno errore
errno = save_errno;
return -1;
}

// ottengo il size del file per la funzione sendfile(2)
off_t n_copied = 0;
struct stat fileinfo = {0};
if (fstat(fd_in, &fileinfo) == -1) {
// errore: salvo errno della fstat(2) precedente e chiudo i file
int save_errno = errno;
close(fd_in);
close(fd_out);

// set errno salvato (evitando quello della close(2)) e ritorno errore
errno = save_errno;
return -1;
}

// copio in kernel-space usando la funzione sendfile(2)
if (sendfile(fd_out, fd_in, &n_copied, fileinfo.st_size) == -1) {
// errore: salvo errno della sendfile(2) precedente e chiudo i file
int save_errno = errno;
close(fd_in);
close(fd_out);

// set errno salvato (evitando quello della close(2)) e ritorno errore
errno = save_errno;
return -1;
}

// chiudo i file
close(fd_in);
close(fd_out);

// ritorno il numero di bytes copiati
return n_copied;
}

Come vedete è quasi sovrapponibile alla precedente ma è diversa proprio nella parte che esegue il lavoro di copia: al posto del loop viene usata la funzione sendfile(2), che ci permette di eseguire una copia diretta e super-efficiente a livello kernel-space (mentre la prima versione lavorava in user-space).

Senza entrare nei dettagli profondi che tutto questo comporta (kernel-space e user-space dei sistemi della famiglia UNIX), mi limito a precisare che questa seconda versione è migliore della prima ma è meno portabile, visto che la sendfile(2) ha comportamenti diversi in base al sistema (per esempio su Linux si può usare solo dal Kernel 2.6.33 in avanti, mentre su macOS funziona solo dalla versione 10.5 in avanti). E già che ci siamo specifichiamo meglio: anche la prima versione (quella con read(2) e write(2)) non è completamente portabile, visto che su alcuni sistemi (tipo quello che comincia con W e che preferisco non nominare neanche...) la system call  read(2) non c'è.

Ok, per oggi può bastare. Potete già intuire che l'argomento del prossimo articolo sarà una versione con buffered I/O della funzione cpFile(), ossia una versione intrinsecamente portabile, già che userà l'I/O standard del C (quello contenuto in stdio.h, per intenderci). Non trattenete il respiro nell'attesa, mi raccomando...

Ciao, e al prossimo post!

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]