Il Server oscuro

I

IL server oscuro
come scrivere un TCP Server in C

Alfred: Pensa che prenderà il Batpod Signore?  
Bruce Wayne: In pieno giorno, Alfred? Non è molto discreto.  
Alfred: La Lamborghini, allora. Molto più discreta.

Questo articolo introduce un argomento un po’ oscuro, proprio come Il Cavaliere Oscuro, e cercheremo di farlo in maniera discreta, anche se, per il momento, non dispongo ancora di una Lamborghini…

…non è un Blog di Cinema questo, neh?…

Come si intuisce dal titolo, oggi parleremo di TCP Server (o Socket Server, fa lo stesso). Questo breve post, insieme al prossimo in cui parleremo (ovviamente) di TCP Client, ci servirà per introdurre adeguatamente gli articoli finali (quelli in cui metteremo realmente molta carne al fuoco) che saranno incentrati su un argomento ancora più oscuro, e cioè OpenSSL. Ma non divaghiamo troppo e concentriamoci sull’argomento di oggi: spero che tutti sappiate cos’è un TCP Server, 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 <errno.h>
#include <unistd.h>
#include <arpa/inet.h>

#define MYBUFSIZE 1024
#define BACKLOG   10      // per listen()

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_STREAM, IPPROTO_TCP)) < 0) {
        // errore socket()
        fprintf(stderr, "%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;              // (locale) 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 per qualunque interfaccia
    server.sin_port = htons(atoi(argv[1])); // set port number del server

    // assegnazione di un indirizzo al socket creato
    if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore bind()
        fprintf(stderr, "%s: errore bind (%s)", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // start ascolto con una coda di max BACKLOG connessioni
    if (listen(my_socket, BACKLOG) < 0) {
        // errore listen()
        fprintf(stderr, "%s: errore listen (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // accetta connessioni da un client entrante
    printf("%s: attesa connessioni entranti...\n", argv[0]);
    socklen_t socksize = sizeof(struct sockaddr_in);
    struct sockaddr_in client;          // (remote) client socket info
    int client_sock;
    if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) {
        // errore accept()
        fprintf(stderr, "%s: errore accept (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // chiude il socket non più in uso
    close(my_socket);

    // loop di ricezione messaggi dal client
    char client_msg[MYBUFSIZE];
    int recv_size;
    while ((recv_size = recv(client_sock, client_msg, MYBUFSIZE, 0)) > 0 ) {
        // send messaggio di ritorno al client
        printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], client_sock, client_msg);
        char server_msg[MYBUFSIZE];
        sprintf(server_msg, "mi hai scritto: %s", client_msg);
        if (send(client_sock, server_msg, strlen(server_msg), 0) < 0) {
            // errore send()
            fprintf(stderr, "%s: errore send (%s)\n", argv[0], strerror(errno));
            close(client_sock);
            return EXIT_FAILURE;
        }

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

    // loop terminato: test motivo
    if (recv_size < 0) {
        // errore recv()
        fprintf(stderr, "%s: errore recv (%s)\n", argv[0], strerror(errno));
        close(client_sock);
        return EXIT_FAILURE;
    }
    else if (recv_size == 0) {
        // Ok: il client si è disconnesso
        printf("%s: client disconnesso\n", argv[0]);
    }

    // esco con Ok
    close(client_sock);
    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 TCP Server:

  1. socket() – crea un socket
  2. prepara la struttura sockaddr_in per questo Server
  3. bind() – assegnazione di un indirizzo al socket creato
  4. listen() – start ascolto con una coda di max BACKLOG connessioni
  5. accept() – accetta connessioni da un Client entrante
  6. recv() – loop di ricezione messaggi dal Client

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 TCP Server che scrivo!), nel loop di lettura c’è il re-invio al Client del messaggio ricevuto: una vera sorpresa! Nel prossimo post, come accennato sopra, vedremo un TCP Client, così chiuderemo il cerchio e potremo testare sul serio una conversazione Client/Server.

Notare, poi, che il main() inizia con un bel test sugli argomenti con eventuale esempio di uso in caso di errore: questo dà la opportuna aria professionale al codice e non dovrebbe mai mancare in una applicazione di questo tipo.

Dal punto di vista strettamente stilistico ho scritto questo codice non rispettando il mio stile preferito (ci sono multipli punti di uscita) ma per un programma quasi-sequenziale come questo non è uno stile disprezzabile, e poi… mi è venuto così! Del resto a suo tempo (vedi il link appena citato) avevo anche detto che ci vuole un po’ di elasticità, mai essere troppo rigidi.

Un ultimo accenno sulla #define BACKLOG: trovare il valore adatto è un argomento quasi filosofico, per cui vi rimando a una ottima descrizione che ho trovato in linea. Ci rivediamo per il TCP Client e, come sempre, non trattenete il respiro nell’attesa…

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

I nostri Partner

Gli articoli più letti

Articoli recenti

Commenti recenti