ZeroMQ Festival – come si usa ZeroMQ in C – pt.2

Z

ZeroMQ Festival
come si usa ZeroMQ in C – pt.2

Sue: Hai sentito che il film di Philippe ha vinto il primo premio al Festival di Colonia, oggi?
Mort: Molto eccitante. Ma Eichmann non era di lì?

E va bene, questa volta ci siamo, è il turno della seconda parte di Rifkin’s Festival (oops: ZeroMQ Festival), come promesso nella prima parte dell’articolo. Qui sopra vi ho proposto un commento secco e lapidario di Mort (Wallace Shawn), il protagonista alter-ego di Woody Allen in questo bel film, un commento che ben introduce questo articolo: dopo aver parlato dell’interfaccia a ZeroMQ di “basso livello“, e cioè la libreria libzmq, è il momento di parlare dell’interfaccia ad “alto livello“, la CZMQ (libczmq). Grazie a CZMQ è possibile scrivere codice di rete sintetico e compatto (anzi, secco e lapidario) anche per problemi complessi, problemi che richiederebbero la scrittura di codice lungo e complicato anche aiutandosi con ZeroMQ low-level (e lunghissimo e complicatissimo usando, ad esempio, i classici BSD Socket).

ZeroMQ
…crepi l’avarizia, oggi ti offrirò un menu high-level…

Allora: CZMQ è una interfaccia C ma è strutturata come se fosse una libreria di classi (nel manuale il termine “class” viene usato frequentemente). Comunque niente paura per chi non è avvezzo al C++: è una interfaccia C al 100%. E questa è la descrizione che danno gli autori stessi della libreria nel manuale:

CZMQ ha questi obiettivi:

- "wrappare" l'API core di ØMQ in una semantica che sia naturale e porti ad applicazioni più brevi e leggibili.
- Nascondere le differenze tra le versioni di ØMQ.
- Fornire uno spazio per lo sviluppo di semantiche API più sofisticate.

E quali sono, in pratica, i vantaggi dell’uso di CZMQ? Facciamo un caso reale: se, per esempio, volessimo risolvere un problema complicato come scrivere un Load Balancing Broker  che segua lo schema di funzionamento seguente (copio una descrizione di questo oggetto da 0MQ – The Guide):

lbbroker

grazie a CZMQ potremmo farlo in maniera abbastanza sintetica e leggibile. Mentre se provassimo a farlo con altri strumenti ci renderemmo subito conto della grande differenza a livello di lunghezza e complicazione. Per quanto riguarda la realizzazione pratica di questo LBB (visto che non amo fare il copia-incolla di cose non mie) vi passo direttamente due link dove viene descritto il codice corrispondente, un doppio codice scritto usando le due librerie (low e high-level): se gli date un occhiata noterete che con CZMQ è decisamente più semplificato e leggibile. Ah, e vi consiglio un utile esercizio: provate a riscrivere (o, perlomeno, a immaginare) lo stesso codice scritto con i BSD Sockettremo solo a pensarci. Ecco i link: versione con lzmq e versione con CZMQ.

E adesso? Ma non posso chiudere un articolo senza proporre nessun codice mio! Quindi ho pensato che per ben descrivere la differenza di approccio tra low-level e high-level è meglio partire dalle basi, e allora vi propongo un esempio semplicissimo di codice Client/Server scritto in tre versioni, BSD Socket, ZeroMQ low-level e CZMQ.

Ho scritto tutti gli esempi a mo’ di codice di test (e non di produzione), quindi sono (ahimè) completamente assenti le importantissime istruzioni di trattamento degli errori (praticamente sono gli scheletri funzionali da completare per entrare in produzione): questo con l’obiettivo di non sviare l’attenzione dal flusso base del codice, quello che ci mostra come si lavora con una libreria rispetto a un’altra. Tutti gli esempi sono composti da due file, client.c e server.c, e sono perfettamente compilabili e funzionanti. Ok, cominciamo con i BSD Socket, vai col codice!

// client.c - un semplice client con BSD Socket
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

// main() - funzione main
int main(void)
{
    // creo il socket
    printf ("Connessione al server...\n");
    int cli_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // preparo la struttura sockaddr_in per il server remoto
    struct sockaddr_in server;
    memset(&server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;                    // set famiglia di indirizzi
    server.sin_addr.s_addr = inet_addr("127.0.0.1");// set indirizzo del server
    server.sin_port = htons(5555);                  // set port del server

    // connetto il socket al server remoto
    connect(cli_sock, (struct sockaddr *)&server, sizeof(server));

    // loop di invio
    int cnt = 0;
    while (cnt++ < 10) {
        // invio la richiesta
        send(cli_sock, "Ping", 5, 0);

        // ricevo la risposta
        char string[10];
        recv(cli_sock, string, sizeof(string), 0);
        printf("risposta ricevuta: %s\n", string);
    }

    // disconnetto
    close(cli_sock);
    return 0;
}
// server.c - un semplice server con BSD Socket
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

// main() - funzione main
int main(void)
{
    // creo il socket TCP
    printf ("Avvio server...\n");
    int srv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // preparo la struttura sockaddr_in per questo server
    struct sockaddr_in server;
    memset(&server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;            // set famiglia di indirizzi
    server.sin_addr.s_addr = INADDR_ANY;    // set indirizzo del server
    server.sin_port = htons(5555);          // set port del server

    // associo l'indirizzo del server al socket e start ascolto
    bind(srv_sock, (struct sockaddr *)&server, sizeof(server));
    listen(srv_sock, 10);

    // accetto connessioni da un client entrante
    socklen_t socksize = sizeof(struct sockaddr_in);
    struct sockaddr_in client;      // struttura sockaddr_in per il client remoto
    int cli_sock = accept(srv_sock, (struct sockaddr *)&client, &socksize);
    close(srv_sock);

    // loop di ricezione
    char string[10];
    while (recv(cli_sock, string, sizeof(string), 0) != -1) {
        printf("richiesta ricevuta: %s\n", string);

        // invio la risposta
        send(cli_sock, "Pong", 5, 0);
    }

    // disconnetto
    close(cli_sock);
    return 0;
}

E passiamo ora al codice in versione libzmq (ZeroMQ low-level):

// client.c - un semplice client ZeroMQ con libzmq
#include <stdio.h>
#include <zmq.h>

// main() - funzione main
int main(void)
{
    // creo il context e il requester
    printf ("Connessione al server...\n");
    void *context = zmq_ctx_new();
    void *requester = zmq_socket(context, ZMQ_REQ);

    // connetto il requester al responder
    zmq_connect(requester, "tcp://localhost:5555");

    // loop di invio
    int cnt = 0;
    while (cnt++ < 10) {
        // invio la richiesta
        zmq_send(requester, "Ping", 5, 0);

        // ricevo la risposta
        char string[10];
        zmq_recv(requester, string, sizeof(string), 0);
        printf("risposta ricevuta: %s\n", string);
    }

    // disconnetto
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}
// server.c - un semplice server ZeroMQ con libzmq
#include <stdio.h>
#include <zmq.h>

// main() - funzione main
int main(void)
{
    // creo il context e il responder
    printf ("Avvio server...\n");
    void *context = zmq_ctx_new();
    void *responder = zmq_socket(context, ZMQ_REP);

    // associo l'indirizzo del responder al contesto
    zmq_bind(responder, "tcp://*:5555");

    // loop di ricezione
    char string[10];
    while (zmq_recv(responder, string, 10, 0) != -1) {
        printf("richiesta ricevuta: %s\n", string);

        // invio la risposta
        zmq_send(responder, "Pong", 5, 0);
    }

    // disconnetto
    zmq_close(responder);
    zmq_ctx_destroy(context);
    return 0;
}

E già questo codice dimostra che ZeroMQ è una libreria che permette di scrivere codice di rete molto semplificato. Ma con CZMQ possiamo snellirlo ancora di più (ma senza aspettarci miracoli: la libzmq già di per sé un passo avanti rispetto al vero low-level, che è rappresentato dai BSD Socket). Vai di nuovo col codice!

// client.c - un semplice client ZeroMQ con CZMQ
#include <stdio.h>
#include <czmq.h>

// main() - funzione main
int main(void)
{
    // create requester
    printf ("Connessione al server...\n");
    zsock_t *requester = zsock_new_req("tcp://localhost:5555");

    // loop di invio
    int cnt = 0;
    while (cnt++ < 10) {
        // invio la richiesta
        zstr_send(requester, "Ping");

        // ricevo la risposta
        char *string;
        string = zstr_recv(requester);
        printf("risposta ricevuta: %s\n", string);
        zstr_free(&string);
    }

    // disconnetto
    zsock_destroy(&requester);
    return 0;
}
// server.c - un semplice server ZeroMQ con CZMQ
#include <stdio.h>
#include <czmq.h>

// main() - funzione main
int main(void)
{
    // creo il responder
    printf ("Avvio server...\n");
    zsock_t *responder = zsock_new_rep("tcp://*:5555");

    // loop di ricezione
    char *string;
    while ((string = zstr_recv(responder)) != NULL) {
        printf("richiesta ricevuta: %s\n", string);
        zstr_free(&string);

        // invio la risposta
        zstr_send(responder, "Pong");
    }

    // disconnetto
    zsock_destroy(&responder);
    return 0;
}

Che ne dite? La differenza tra le tre versioni, pur non essendo eclatante, è evidente, e si nota una filosofia di programmazione abbastanza diversa. E, a parte le considerazioni filosofiche, alla fin fine si può dire che nel percorso da BSD Socket a libzmq a CZMQ, le linee di codice vanno in diminuzione, e questo è il fattore molto importante che stavamo ricercando.

Ok, per oggi può bastare: ZeroMQ passa, a pieni voti, l’esame di “ottima libreria per Software di rete“. Lo passa bene già con la libreria low-level e “più meglio” (ah ah ah) con CZMQ, la libreria high-level. Ma, nonostante questo, vi invito a non dimenticare che, sotto sotto (anche sotto ZeroMQ!), ci sono i mitici e insostituibili BSD Socket che possono essere ancora usati direttamente per scrivere ottimo e soddisfacente codice di rete, magari solo un po’ più complicato (da scrivere e da leggere). De gustibus…

Ciao, e al prossimo post!

A proposito di me

Aldo Abate

È un Cinefilo prestato alla Programmazione in C. Per mancanza di tempo ha rinunciato ad aprire un CineBlog (che richiede una attenzione quasi quotidiana), quindi ha deciso di dedicarsi a quello che gli riesce meglio con il poco tempo a disposizione: scrivere articoli sul suo amato C infarcendoli di citazioni Cinematografiche. Il risultato finale è ambiguo e spiazzante, ma lui conta sul fatto che il (si spera) buon contenuto tecnico induca gli appassionati di C a leggere ugualmente gli articoli ignorando la parte Cinefila.
Ma in realtà il suo obiettivo principale è che almeno un lettore su cento scopra il Cinema grazie al C...

Di Aldo Abate

Gli articoli più letti

Articoli recenti

Commenti recenti