Il Client oscuro

I

Il Client oscuro
come scrivere un TCP Client in C

Gambol: Dammi una ragione per non dire ai miei uomini di staccarti la testa.
Jocker: Ti va bene un trucchetto di magia? Adesso questa matita la faccio sparire!
Dopo il Il Cavaliere Oscuro  (anzi, Il Server Oscuro) è venuto il turno del Client Oscuro. Per i lettori più impressionabili ometto come il Jocker ha fatto sparire la matita… chi ha visto il film se lo ricorderà di sicuro. Ed ora una piccola premessa prima di passare al post vero e proprio: se durante la lettura vi prende un senso di déjà vu, di smarrimento, come se steste rivivendo qualcosa del passato… ecco, non vi preoccupate: visto che l’argomento di questo post è quasi identico (anzi, speculare) a quello del post precedente ho pensato di riciclarlo quasi completamente, sostituendo solo alcune parti (oggi sono un po’ pigro…). Anche il codice, come vedrete è molto simile (i Server e i Client si somigliano molto). E termino la premessa con una nota per chi non avesse ancora letto la prima parte: vergogna! Subito a leggerla! Se no non capite di cosa stiamo parlando…
…con questa matita vi scriverò un post sui Client in C…

Come si intuisce dal titolo questa volta è il turno del TCP Client (o Socket Client, fa lo stesso). 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 <errno.h>
#include <unistd.h>
#include <arpa/inet.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_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 il server remoto
    struct sockaddr_in server;                      // (remoto) 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 port number del server

    // connessione al server remoto
    if (connect(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore connect()
        fprintf(stderr, "%s: errore connect (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // 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 (send(my_socket, my_msg, strlen(my_msg), 0) < 0) {
            // errore send()
            fprintf(stderr, "%s: errore send (%s)\n", argv[0], strerror(errno));
            close(my_socket);
            return EXIT_FAILURE;
        }

        // riceve una risposta dal server remoto
        memset(my_msg, 0, MYBUFSIZE);
        if (recv(my_socket, my_msg, MYBUFSIZE, 0) < 0) {
            // errore recv()
            fprintf(stderr, "%s: errore recv (%s)\n", argv[0], strerror(errno));
            close(my_socket);
            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 TCP Client:

  1. socket() – crea un socket
  2. prepara la struttura sockaddr_in per il server remoto
  3. connect() – connessione al server remoto
  4. send() + recv() – 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 TCP 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@mylinux:~/blogtest$ ./tcpserver 8888
./tcpserver: attesa connessioni entranti...
./tcpserver: ricevuto messaggio dal sock 4: pippo
./tcpserver: ricevuto messaggio dal sock 4: pluto
./tcpserver: client disconnesso

terminale 2 (Client):

aldo@mylinux:~/blogtest$ ./tcpclient 127.0.0.1 8888
Scrivi un messaggio per il Server remoto: pippo
./tcpclient: risposta Server: mi hai scritto: pippo
Scrivi un messaggio per il Server remoto: pluto
./tcpclient: 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 se ne accorge. Missione compiuta!

Ecco, con questo post abbiamo completato la presentazione, rapida e concisa, del codice per TCP Server e Client in C (con Berkeley Sockets). Come ho accennato nell’articolo precedente questa è una presentazione propedeutica al vero obbiettivo della trattazione: scrivere TCP Server e Client sicuri in C usando OpenSSL. Lo vedremo presto su questi schermi…

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