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.

lunedì 12 ottobre 2015

Il Client oscuro - Il ritorno
come scrivere un Socket Client in C

Oggi non avevo voglia di scervellarmi per trovare un titolo adatto al post per cui ho pensato di riciclare il titolo (e anche parte del testo) del post precedente, di cui questo è, evidentemente, il seguito (come promesso, eh!). Per chi non avesse letto la prima parte... vergogna! Subito a leggerla! Se no non capite di cosa stiamo parlando...
anche Bane si chiede: non è un Blog di Cinema questo, neh?
Come si intuisce dal titolo questa volta è il turno del Socket Client (o TCP 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 <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 9999]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // crea un socket
    int my_socket;
    if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        // errore socket()
        printf("%s: could not create 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
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));

    // connessione al server remoto
    if (connect(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore connect()
        printf("%s: connect failed (%s)\n", argv[0], strerror(errno));
        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()
            printf("%s: send failed (%s)\n", argv[0], strerror(errno));
            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()
            printf("%s: recv failed (%s)\n", argv[0], strerror(errno));
            return EXIT_FAILURE;
        }

        // mostra la risposta
        printf("%s: Server reply: %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 Socket 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 Socket 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@linux-nb:~/blogtest$ ./socket_server 9999
./socket_server: attesa connessioni entranti...
./socket_server: ricevuto messaggio dal sock 4: pippo
./socket_server: ricevuto messaggio dal sock 4: pluto
./socket_server: client disconnected
terminale 2 (Client):
aldo@linux-nb:~/blogtest$ ./socket_client 127.0.0.1 9999
Scrivi un messaggio per il Server remoto: pippo   
./socket_client: Server reply: mi hai scritto: pippo
Scrivi un messaggio per il Server remoto: pluto
./socket_client: Server reply: 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!

Ciao e al prossimo post!

Nessun commento:

Posta un commento