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!
…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:
- socket() – crea un socket
- prepara la struttura sockaddr_in per il server remoto
- connect() – connessione al server remoto
- 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!