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.

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!