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.

martedì 23 agosto 2016

UDPhunter
come scrivere un UDP Client in C - pt.2

Siamo alla fine di Agosto e il ritorno dalle vacanze è sempre duro. Quindi, per risparmiare tempo e fatica cercherò di riciclare parte del testo di un mio vecchio post ben collegato a questo. Del resto questa è la seconda parte (il Client) dei post su UDP e, quindi, la seconda parte dei miei vecchi post su TCP cade a fagiolo. Il codice è, ovviamente, tutto nuovo.
oggi parleremo degli UDP client...
Come si intuisce dal titolo questa volta è il turno del UDP Client. Spero che tutti sappiate cos'è, se no vi consiglio una utile lettura con tanto di esempio (ma il mio esempio è meglio!), così non perdo tempo e posso passare direttamente al codice. Eccolo!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

#define MYBUFSIZE 1024

int main(int argc, char *argv[])
{
    // test argomenti
    if (argc != 3) {
        // errore args
        printf("%s: numero argomenti errato\n", argv[0]);
        printf("uso: %s host port [i.e.: %s 127.0.0.1 8888]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // crea un socket
    int my_socket;
    if ((my_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        // errore socket()
        printf("%s: non posso creare il socket (%s)\n", argv[0], strerror(errno));
        return EXIT_FAILURE;
    }

    // prepara la struttura sockaddr_in per il server remoto
    struct sockaddr_in server;                      // (remote) server socket info
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;                    // set address family
    server.sin_addr.s_addr = inet_addr(argv[1]);    // set server address
    server.sin_port = htons(atoi(argv[2]));         // set server port number

    // loop di comunicazione col server remoto
    for (;;) {
        // compone messaggio per il server remoto
        char my_msg[MYBUFSIZE];
        printf("Scrivi un messaggio per il Server remoto: ");
        scanf("%s", my_msg);

        // send messaggio al server remoto
        if (sendto(my_socket, my_msg, strlen(my_msg), 0, (struct sockaddr *)&server, sizeof(server)) < 0) {
            // errore send()
            printf("%s: errore send (%s)\n", argv[0], strerror(errno));
            return EXIT_FAILURE;
        }

        // riceve una risposta dal server remoto
        memset(my_msg, 0, MYBUFSIZE);
        if (recvfrom(my_socket, my_msg, MYBUFSIZE, 0, NULL, NULL) < 0) {
            // errore recv()
            printf("%s: errore recv (%s)\n", argv[0], strerror(errno));
            return EXIT_FAILURE;
        }

        // mostra la risposta
        printf("%s: risposta Server: %s\n", argv[0], my_msg);
    }

    // esco con Ok
    return EXIT_SUCCESS;
}
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.

La struttura è quella classica e basica di un UDP Client:
  1. socket() - crea un socket
  2. prepara la struttura sockaddr_in per il server remoto
  3. sendto() + recvfrom() - loop di comunicazione col server remoto
ovviamente esistono varianti di questa struttura, ma questa è quella classica. In quest'esempio, che ho scritto e testato appositamente per il blog (beh, in realtà ho adattato/modificato ad uso blog un po' di codice che ho scritto per lavoro, non è certo il primo UDP Client che scrivo!), nel loop di comunicazione c'è anche la lettura della risposta del Server, così chiudiamo il cerchio con il post precedente e possiamo testare sul serio una conversazione Client/Server.

Per quanto riguarda il flusso e lo stile del main() valgono le note elencate nel post sul Server. Per testarlo è sufficiente aprire due terminali (UNIX o Linux, ovviamente), e avviare in uno il Server e nell'altro il Client; se proviamo in una macchina sola il Client deve, come è logico, collegarsi al Server su localhost. Per l'argomento port si può usare un numero qualsiasi scelto tra quelli non riservati (e bisogna usare lo stesso port per Client e Server!) Il risultato sarà il seguente:

terminale 1 (Server):
aldo@ao-linux-nb:~/blogtest$ ./udpserver 8888
./udpserver: ricevuto messaggio dal sock 3: pippo
./udpserver: ricevuto messaggio dal sock 3: pluto
terminale 2 (Client):
aldo@ao-linux-nb:~/blogtest$ ./udpclient 127.0.0.1 8888
Scrivi un messaggio per il Server remoto: pippo
./udpclient: risposta Server: mi hai scritto: pippo
Scrivi un messaggio per il Server remoto: pluto
./udpclient: risposta Server: mi hai scritto: pluto
Scrivi un messaggio per il Server remoto: ^C
Come si nota il Client e il Server si parlano e quando il Client si scollega (con un brutale Ctrl-C) il Server non se ne accorge (è una comunicazione connectionless). Missione compiuta!

Ciao e al prossimo post!

sabato 9 luglio 2016

UDPhunter
come scrivere un UDP Server in C - pt.1

Spesso si può fare la stessa cosa in due (o magari 1000...) maniere differenti. Non necessariamente buone tutte. Ad esempio da un buon romanzo come Red Dragon sono stati tratti due film: uno è un capolavoro di Michael Mann, mentre l'altro è una vera schifezza che sarebbe stato meglio non girare.
Mr UDP, Mr TCP e la fotografia abbagliante di un capolavoro
Quando pensiamo alle comunicazioni basate sui Socket pensiamo sempre ai TCP Client/Server, e ci dimentichiamo dell'esistenza di UDP. Ecco, con UDP si possono fare cose eccellenti, sempre considerando quello che dice il manuale:
...It implements a connectionless, unreliable datagram packet
service.  Packets may be reordered or duplicated before they arrive.
UDP generates and checks checksums to catch transmission errors...
Non facciamoci tradire dalla (preoccupante) parola unreliable: se lo usiamo nelle applicazioni giuste (quelle dove la perdita/duplicazione di un pacchetto non è un errore tragico... il VoIP, per esempio) le doti di UDP (velocità, leggerezza, facilità di implementazione) vengono fuori alla grande.

Per cui vi propongo un UDP Server che è quasi una copia del TCP server visto qui, e che già a prima vista si nota più semplice. Vai col codice!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

#define MYBUFSIZE 1024

int main(int argc, char *argv[])
{
    // test argomenti
    if (argc != 2) {
        // errore args
        printf("%s: numero argomenti errato\n", argv[0]);
        printf("uso: %s port [i.e.: %s 8888]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // crea un socket
    int my_socket;
    if ((my_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        // errore socket()
        printf("%s: non posso creare il socket (%s)\n", argv[0], strerror(errno));
        return EXIT_FAILURE;
    }

    // prepara la struttura sockaddr_in per questo server
    struct sockaddr_in server;              // (local) server socket info
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;            // set address family
    server.sin_addr.s_addr = INADDR_ANY;    // set server address for any interface
    server.sin_port = htons(atoi(argv[1])); // set server port number

    // bind informazioni del server al socket
    if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore bind()
        printf("%s: errore bind (%s)", argv[0], strerror(errno));
        return EXIT_FAILURE;
    }

    // loop di ricezione messaggi dal client
    struct sockaddr_in client;  // appoggio per dati del client per poter rispondere
    socklen_t socksize = sizeof(struct sockaddr_in);
    char client_msg[MYBUFSIZE];
    while (recvfrom(my_socket, client_msg, MYBUFSIZE, 0, (struct sockaddr *)&client, &socksize) > 0) {
        // send messaggio di ritorno al client
        printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], my_socket, client_msg);
        char server_msg[MYBUFSIZE];
        sprintf(server_msg, "mi hai scritto: %s", client_msg);
        if (sendto(my_socket, server_msg, strlen(server_msg), 0, (struct sockaddr *)&client, sizeof(client)) < 0) {
            // errore send()
            printf("%s: errore send (%s)\n", argv[0], strerror(errno));
            return EXIT_FAILURE;
        }

        // clear buffer
        memset(client_msg, 0, MYBUFSIZE);
    }

    // errore recv(): il "client disconnected" con recv_size==0 non esiste perché questo server è connectionless
    printf("%s: errore recv (%s)\n", argv[0], strerror(errno));
    return EXIT_FAILURE;
}
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.

La struttura è quella classica e basica di un UDP Server
  1. socket() - crea un socket
  2. prepara la struttura sockaddr_in per questo server
  3. bind() - bind informazioni del server al socket
  4. recvfrom() - loop di ricezione messaggi dal client
In quest'esempio, che ho scritto e testato appositamente per il blog, nel loop di lettura c'è il re-invio al Client del messaggio ricevuto. Nel prossimo post vedremo il Client, così chiuderemo il cerchio e potremo testare sul serio una conversazione UDP Client/Server.

Come (spero) avrete notato, invece delle funzioni send/recv si usano sendto/recvfrom (che sono identiche alle controparti TCP ma aggiungono i dati dei sender/receiver) e, inoltre, mancano le fasi di listen e accept, il che rende il Server più semplice da scrivere e anche da usare: si possono fare sequenze di start/stop/restart di Server e Client nell'ordine che si vuole e la comunicazione, magicamente, si restaurerà sempre, mentre la versione TCP (immagino l'abbiate testata, e se no: correte a farlo!) che ha una fase di connessione, necessita di una più rigorosa sequenza di start/stop per funzionare.

Ci rivediamo per il UDP Client e, come sempre, non trattenete il respiro nell'attesa...

Ciao e al prossimo post!

venerdì 3 giugno 2016

Sprintf Driver
perché non bisogna usare la sprintf in C

"Ma dici a me? Ma dici a me?... Ma dici a me?...". Si, proprio come il grande De Niro in Taxi Driver, questa è stata la mia reazione (incredula) quando ho scoperto (molti anni fa, oramai) che, dopo anni e anni di onorato uso, avrei dovuto smettere di usare la sprintf().
...e tu dici a me di non usare più la sprintf? A me?...
Beh, in effetti se la usi bene e hai il 100% di controllo sul codice scritto puoi anche usarla senza grossi problemi ma, come dicono gli inglesi, la sprintf() è error prone, induce facilmente a errori, anche gravi. Il problema più grave ed evidente con la sprintf() si chiama buffer overflow, e non credo che sia necessario spenderci molte parole: se il buffer che passiamo come primo argomento non è correttamente dimensionato il disastro è dietro l'angolo.

Fortunatamente ci viene in aiuto la snprintf(), che è della stessa famiglia ma molto più sicura. Vediamo i due prototipi a confronto:
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
La snprintf() ci obbliga a mettere il size del buffer come secondo argomento, per cui è facilissimo prendere l'abitudine a scrivere in un modo error-free come questo:
char buffer[32];
snprintf(buffer, sizeof(buffer), "Hello world!");
Se al posto di "Hello world!" avessimo scritto una stringa di più di 32 chars, nessun problema: la snprintf() la tronca opportunamente e siamo salvi.

E adesso vi propongo un piccolo esempio reale: prendiamo una nostra vecchia conoscenza scritta per un vecchio post, la getDateUsec() e la scriviamo in due versioni, una buona e una cattiva (bad). Vediamo:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

// prototipi locali
char *getDateUsec(char *dest, size_t size);
char *badGetDateUsec(char *dest);

// funzione main
int main(int argc, char* argv[])
{
    // chiama getDateUsec (o badGetDateUsec) e scrive il risultato
    char dest[12];
    printf("date con usec: %s\n", getDateUsec(dest, sizeof(dest)));
    //printf("date con usec: %s\n", badGetDateUsec(dest));

    return EXIT_SUCCESS;
}

// getDateUsec() - Genera una stringa con data e ora (usa i microsecondi)
char *getDateUsec(char *dest, size_t size)
{
    // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    snprintf(dest, size, fmt, tv.tv_usec);

    // return stringa destinazione dest
    return dest;
}

// badGetDateUsec() - Genera una stringa con data e ora (usa i microsecondi) (versione bad)
char *badGetDateUsec(char *dest)
{
    // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    sprintf(dest, fmt, tv.tv_usec);

    // return stringa destinazione dest
    return dest;
}
Ecco, a suo tempo, per semplificare, avevo scritto una getDateUsec() che era, in realtà, una badGetDateUsec() (e in seguito, per precisione, ho provveduto a modificarla anche sul post). Quella versione funzionava ma poteva creare problemi, mentre la nuova versione è molto più sicura. Provate a compilare l'esempio, dove, volutamente, ho sottodimensionato il buffer di destinazione: commentando la badGetDateUsec() e usando la getDateUsec(), funziona perfettamente, troncando l'output a 12 chars. Se, invece, commentate la getDateUsec() e usate la badGetDateUsec() il programma si schianta durante l'esecuzione. Provare per credere!

E già che siamo in argomento sprintf() un piccolo consiglio un po' OT: se dovete aggiungere sequenzialmente delle stringhe (in un loop, ad esempio) su una stringa base (per comporre un testo, ad esempio) non fate mai cosi:
char buf[256] = "";
for (int i = 0; i < 5; i++)
    sprintf(buf, "%s aggiunto alla stringa %d\n", buf, i);
il metodo qui sopra sembra funzionare, ma, in realtà, funziona quando c'ha voglia lui. Fate invece così:
char buf[256] = "";
for (int i = 0; i < 5; i++) {
    char tmpbuf[256];
    sprintf(tmpbuf, "%s aggiunto alla stringa %d\n", buf, i);
    sprintf(buf, "%s", tmpbuf);
}
E se non ci credete provate a passare il codice con un lint tipo cppchek (che è sempre una buona idea) o consultate il manuale della sprintf():
C99 and POSIX.1-2001 specify that the results are undefined if  a  call
to  sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause copy‐
ing to take place between objects that overlap  (e.g.,  if  the  target
string  array and one of the supplied input arguments refer to the same
buffer).
E, ovviamente, anche in quest'ultimo esempio (fatto per semplicità con la sprintf()) sarebbe raccomandabile usare la snprintf().

Ciao e al prossimo post!