Versione finale del client ECHO su TCP, con esempio di uso della funzione
authorSimone Piccardi <piccardi@gnulinux.it>
Mon, 20 Oct 2003 22:44:16 +0000 (22:44 +0000)
committerSimone Piccardi <piccardi@gnulinux.it>
Mon, 20 Oct 2003 22:44:16 +0000 (22:44 +0000)
shutdown. Revisionati i nomi delle relative figure

listati/ClientEcho.c
listati/ClientEcho_first.c [new file with mode: 0644]
sources/TCP_echo.c
sources/TCP_echo_third.c [new file with mode: 0644]
tcpsock.tex
tcpsockadv.tex

index 9a18289..520671f 100644 (file)
@@ -1,12 +1,51 @@
 void ClientEcho(FILE * filein, int socket) 
 {
     char sendbuff[MAXLINE+1], recvbuff[MAXLINE+1];
-    int nread; 
-    while (fgets(sendbuff, MAXLINE, filein) != NULL) {
-        FullWrite(socket, sendbuff, strlen(sendbuff)); 
-        nread = read(socket, recvbuff, strlen(sendbuff));        
-        recvbuff[nread] = 0;
-        fputs(recvbuff, stdout);
+    int nread, nwrite; 
+    int maxfd;
+    fd_set fset;
+    int eof = 0;
+    /* initialize file descriptor set */
+    FD_ZERO(&fset);
+    maxfd = max(fileno(filein), socket) + 1;
+    while (1) {
+       FD_SET(socket, &fset);         /* set for the socket */
+       if (eof == 0) {
+           FD_SET(fileno(filein), &fset); /* set for the standard input */
+       }
+       select(maxfd, &fset, NULL, NULL, NULL); /* wait for read ready */
+       if (FD_ISSET(fileno(filein), &fset)) {  /* if ready on stdin */
+           if (fgets(sendbuff, MAXLINE, filein) == NULL) { /* if no input */
+               eof = 1;               /* EOF on input */
+               shutdown(socket, SHUT_WR);      /* close write half */
+               FD_CLR(fileno(filein), &fset);  /* no more interest on stdin */
+           } else {                   /* else we have to write to socket */
+               nwrite = FullWrite(socket, sendbuff, strlen(sendbuff)); 
+               if (nwrite < 0) {      /* on error stop */
+                   printf("Errore in scrittura: %s", strerror(errno));
+                   return;
+               }
+           }
+       }
+       if (FD_ISSET(socket, &fset)) { /* if ready on socket */ 
+           nread = read(socket, recvbuff, strlen(sendbuff)); /* do read */
+           if (nread < 0) {  /* error condition, stop client */
+               printf("Errore in lettura: %s\n", strerror(errno));
+               return;
+           }
+           if (nread == 0) { /* server closed connection, stop */
+               if (eof == 1) {
+                   return;
+               } else {
+                   printf("EOF prematuro sul socket\n");
+                   return;
+               }
+           }
+           recvbuff[nread] = 0;   /* else read is ok, write on stdout */
+           if (fputs(recvbuff, stdout) == EOF) {
+               perror("Errore in scrittura su terminale");
+               return;
+           }
+       }
     }
-    return;
 }
diff --git a/listati/ClientEcho_first.c b/listati/ClientEcho_first.c
new file mode 100644 (file)
index 0000000..9a18289
--- /dev/null
@@ -0,0 +1,12 @@
+void ClientEcho(FILE * filein, int socket) 
+{
+    char sendbuff[MAXLINE+1], recvbuff[MAXLINE+1];
+    int nread; 
+    while (fgets(sendbuff, MAXLINE, filein) != NULL) {
+        FullWrite(socket, sendbuff, strlen(sendbuff)); 
+        nread = read(socket, recvbuff, strlen(sendbuff));        
+        recvbuff[nread] = 0;
+        fputs(recvbuff, stdout);
+    }
+    return;
+}
index 57d6184..691911e 100644 (file)
@@ -26,7 +26,7 @@
  *
  * Usage: echo -h give all info's
  *
- * $Id: TCP_echo.c,v 1.10 2003/10/19 10:38:27 piccardi Exp $
+ * $Id: TCP_echo.c,v 1.11 2003/10/20 22:44:16 piccardi Exp $
  *
  ****************************************************************/
 /* 
@@ -142,16 +142,21 @@ void ClientEcho(FILE * filein, int socket)
     int nread, nwrite; 
     int maxfd;
     fd_set fset;
+    int eof = 0;
     /* initialize file descriptor set */
     FD_ZERO(&fset);
     maxfd = max(fileno(filein), socket) + 1;
     while (1) {
        FD_SET(socket, &fset);         /* set for the socket */
-       FD_SET(fileno(filein), &fset); /* set for the standard input */
+       if (eof == 0) {
+           FD_SET(fileno(filein), &fset); /* set for the standard input */
+       }
        select(maxfd, &fset, NULL, NULL, NULL); /* wait for read ready */
        if (FD_ISSET(fileno(filein), &fset)) {  /* if ready on stdin */
            if (fgets(sendbuff, MAXLINE, filein) == NULL) { /* if no input */
-               return;                /* we stopped client */
+               eof = 1;               /* EOF on input */
+               shutdown(socket, SHUT_WR);      /* close write half */
+               FD_CLR(fileno(filein), &fset);  /* no more interest on stdin */
            } else {                   /* else we have to write to socket */
                nwrite = FullWrite(socket, sendbuff, strlen(sendbuff)); 
                if (nwrite < 0) {      /* on error stop */
@@ -167,8 +172,12 @@ void ClientEcho(FILE * filein, int socket)
                return;
            }
            if (nread == 0) { /* server closed connection, stop */
-               printf("EOF sul socket\n");
-               return;
+               if (eof == 1) {
+                   return;
+               } else {
+                   printf("EOF prematuro sul socket\n");
+                   return;
+               }
            }
            recvbuff[nread] = 0;   /* else read is ok, write on stdout */
            if (fputs(recvbuff, stdout) == EOF) {
diff --git a/sources/TCP_echo_third.c b/sources/TCP_echo_third.c
new file mode 100644 (file)
index 0000000..680e0e8
--- /dev/null
@@ -0,0 +1,180 @@
+/* TCP_echo.c
+ * 
+ * Copyright (C) 2001-2003 Simone Piccardi
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/****************************************************************
+ *
+ * Program TCP_echo.c
+ * Simple TCP client for echo service (port 7)
+ *
+ * Author: Simone Piccardi
+ * Jun. 2001
+ *
+ * Usage: echo -h give all info's
+ *
+ * $Id: TCP_echo_third.c,v 1.1 2003/10/20 22:44:16 piccardi Exp $
+ *
+ ****************************************************************/
+/* 
+ * Include needed headers
+ */
+#include <sys/types.h>   /* predefined types */
+#include <unistd.h>      /* include unix standard library */
+#include <arpa/inet.h>   /* IP addresses conversion utiliites */
+#include <sys/socket.h>  /* socket library */
+#include <stdio.h>      /* include standard I/O library */
+#include <errno.h>      /* include error codes */
+#include <string.h>     /* include erroro strings definitions */
+
+#include "macros.h"
+
+#define MAXLINE 256
+void usage(void);
+void ClientEcho(FILE * filein, int socket);
+void SigTERM_hand(int sig);
+
+/* Program begin */
+int main(int argc, char *argv[])
+{
+/* 
+ * Variables definition  
+ */
+    int sock, i;
+    int reset = 0;
+    struct sockaddr_in serv_add;
+    struct linger ling;
+    /*
+     * Input section: decode parameters passed in the calling 
+     * Use getopt function
+     */
+    opterr = 0;         /* don't want writing to stderr */
+    while ( (i = getopt(argc, argv, "hr")) != -1) {
+       switch (i) {
+       /* 
+        * Handling options 
+        */ 
+       case 'h':  
+           printf("Wrong -h option use\n");
+           usage();
+           return(1);
+           break;
+       case 'r':
+           reset = 1;
+           break;
+       case '?':   /* unrecognized options */
+           printf("Unrecognized options -%c\n",optopt);
+           usage();
+       default:    /* should not reached */
+           usage();
+       }
+    }
+    /* ***********************************************************
+     * 
+     *          Options processing completed
+     *
+     *               Main code beginning
+     * 
+     * ***********************************************************/
+    /* create socket */
+    if ( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+       perror("Socket creation error");
+       return 1;
+    }
+    /* initialize address */
+    memset((void *) &serv_add, 0, sizeof(serv_add)); /* clear server address */
+    serv_add.sin_family = AF_INET;                   /* address type is INET */
+    serv_add.sin_port = htons(7);                    /* echo port is 7 */
+    /* build address using inet_pton */
+    if ( (inet_pton(AF_INET, argv[optind], &serv_add.sin_addr)) <= 0) {
+       perror("Address creation error");
+       return 1;
+    }
+    /* extablish connection */
+    if (connect(sock, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
+       perror("Connection error");
+       return 1;
+    }
+    /* check if resetting on close is required */
+    if (reset) {
+       printf("Setting reset on close \n");
+       ling.l_onoff = 1;
+       ling.l_linger = 0;      
+       if (setsockopt(sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling))) {
+           perror("Cannot set linger");
+           exit(1);
+       }
+    }
+    /* do read/write operations */
+    ClientEcho(stdin, sock);
+    /* normal exit */
+    return 0;
+}
+/*
+ * routine to print usage info and exit 
+ */
+void usage(void) {
+    printf("Take daytime from a remote host \n");
+    printf("Usage:\n");
+    printf("  daytime [-h] [-v] [host in dotted decimal form] \n");
+//    printf("  -v        set verbosity on\n");
+    printf("  -r          require reset on closing\n");
+    printf("  -h          print this help\n");
+    exit(1);
+}
+
+void ClientEcho(FILE * filein, int socket) 
+{
+    char sendbuff[MAXLINE+1], recvbuff[MAXLINE+1];
+    int nread, nwrite; 
+    int maxfd;
+    fd_set fset;
+    /* initialize file descriptor set */
+    FD_ZERO(&fset);
+    maxfd = max(fileno(filein), socket) + 1;
+    while (1) {
+       FD_SET(socket, &fset);         /* set for the socket */
+       FD_SET(fileno(filein), &fset); /* set for the standard input */
+       select(maxfd, &fset, NULL, NULL, NULL); /* wait for read ready */
+       if (FD_ISSET(fileno(filein), &fset)) {  /* if ready on stdin */
+           if (fgets(sendbuff, MAXLINE, filein) == NULL) { /* if no input */
+               return;                /* we stopped client */
+           } else {                   /* else we have to write to socket */
+               nwrite = FullWrite(socket, sendbuff, strlen(sendbuff)); 
+               if (nwrite < 0) {      /* on error stop */
+                   printf("Errore in scrittura: %s", strerror(errno));
+                   return;
+               }
+           }
+       }
+       if (FD_ISSET(socket, &fset)) { /* if ready on socket */ 
+           nread = read(socket, recvbuff, strlen(sendbuff)); /* do read */
+           if (nread < 0) {  /* error condition, stop client */
+               printf("Errore in lettura: %s\n", strerror(errno));
+               return;
+           }
+           if (nread == 0) { /* server closed connection, stop */
+               printf("EOF sul socket\n");
+               return;
+           }
+           recvbuff[nread] = 0;   /* else read is ok, write on stdout */
+           if (fputs(recvbuff, stdout) == EOF) {
+               perror("Errore in scrittura su terminale");
+               return;
+           }
+       }
+    }
+}
index 8b2be4f..2ce01cd 100644 (file)
@@ -1694,7 +1694,7 @@ scriverli su \file{stdout}.
 \begin{figure}[!htb]
   \footnotesize \centering
   \begin{minipage}[c]{15.6cm}
-    \includecodesample{listati/ClientEcho.c}
+    \includecodesample{listati/ClientEcho_first.c}
   \end{minipage} 
   \normalsize
   \caption{Codice della prima versione della funzione \texttt{ClientEcho} per 
@@ -2082,8 +2082,8 @@ riscrittura parziale del server, la nuova versione di questo, in cui si sono
 introdotte una serie di nuove opzioni che ci saranno utili per il debug, è
 mostrata in \figref{fig:TCP_echo_server_code_second}, dove si sono riportate
 la sezioni di codice modificate nella seconda versione del programma, il
-sorgente completo di quest'ultimo si trova nel file
-\file{TCP\_echod\_second.c} dei sorgenti allegati alla guida.
+codice completo di quest'ultimo si trova nel file \file{TCP\_echod\_second.c}
+dei sorgenti allegati alla guida.
 
 La prima modifica effettuata è stata quella di introdurre una nuova opzione a
 riga di comando, \texttt{-c}, che permette di richiedere il comportamento
index e77e432..d22c6aa 100644 (file)
@@ -182,7 +182,9 @@ Riprendiamo allora il codice del client, modificandolo per l'uso di
 di \figref{fig:TCP_ClientEcho_second}, dato che tutto il resto, che riguarda
 le modalità in cui viene stabilita la connessione con il server, resta
 assolutamente identico. La nostra nuova versione di \func{ClientEcho}, la
-terza della serie, è riportata in \figref{fig:TCP_ClientEcho_third}.
+terza della serie, è riportata in \figref{fig:TCP_ClientEcho_third}, il codice
+completo si trova nel file \file{TCP\_echo\_third.c} dei sorgenti allegati alla
+guida.
 
 In questo caso la funzione comincia (\texttt{\small 8--9}) con l'azzeramento
 del file descriptor set \var{fset} e l'impostazione del valore \var{maxfd}, da
@@ -299,8 +301,6 @@ del client sar
 \func{select} per la ricezione di un errore di \errcode{ECONNRESET}.
 
 
-
-
 \subsection{La funzione \func{shutdown}}
 \label{sec:TCP_shutdown}
 
@@ -322,7 +322,180 @@ Questa 
 Il problema che si pone è che se la chiusura del socket è effettuata con la
 funzione \func{close}, come spiegato in \secref{sec:TCP_func_close}, si perde
 ogni possibilità di poter rileggere quanto l'altro capo può continuare a
-scrivere. Per poter permettere allora 
+scrivere. Per poter permettere allora di segnalare che si è concluso con la
+scrittura, continuando al contempo a leggere quanto può provenire dall'altro
+capo del socket si può allora usare la funzione \funcd{shutdown}, il cui
+prototipo è:
+\begin{prototype}{sys/socket.h}
+{int shutdown(int sockfd, int how)}
+
+Chiude un lato della connessione fra due socket.
+  
+  \bodydesc{La funzione restituisce zero in caso di successo e -1 per un
+    errore, nel qual caso \var{errno} assumerà i valori:
+  \begin{errlist}
+  \item[\errcode{ENOTSOCK}] il file descriptor non corrisponde a un socket.
+  \item[\errcode{ENOTCONN}] il socket non è connesso.
+  \end{errlist}
+  ed inoltre \errval{EBADF}.}
+\end{prototype}
+
+La funzione prende come primo argomento il socket \param{sockfd} su cui si
+vuole operare e come secondo argomento un valore intero \param{how} che indica
+la modalità di chiusura del socket, quest'ultima può prendere soltanto tre
+valori: 
+\begin{basedescript}{\desclabelwidth{2.2cm}\desclabelstyle{\nextlinelabel}}
+\item[\macro{SHUT\_RD}] chiude il lato in lettura del socket, non sarà più
+  possibile leggere dati da esso, tutti gli eventuali dati trasmessi
+  dall'altro capo del socket saranno automaticamente scartati dal kernel, che,
+  in caso di socket TCP, provvederà comunque ad inviare i relativi segmenti di
+  ACK.
+\item[\macro{SHUT\_WR}] chiude il lato in scrittura del socket, non sarà più
+  possibile scrivere dati su di esso. Nel caso di socket TCP la chiamata causa
+  l'emissione di un segmento FIN, secondo la procedura chiamata
+  \textit{half-close}. Tutti i dati presenti nel buffer di scrittura prima
+  della chiamata saranno inviati, seguiti dalla sequenza di chiusura
+  illustrata in \secref{sec:TCP_conn_term}.
+\item[\macro{SHUT\_RDWR}] chiude sia il lato in lettura che quello in
+  scrittura del socket. È equivalente alla chiamata in sequenza con
+  \macro{SHUT\_RD} e \macro{SHUT\_WR}.
+\end{basedescript}
+
+Ci si può chiedere quale sia l'utilità di avere introdotto \macro{SHUT\_RDWR}
+quando questa sembra rendere \funcd{shutdown} del tutto equivalente ad una
+\func{close}. In realtà non è così, esiste infatti un'altra differenza con
+\func{close}, più sottile. Finora infatti non ci siamo presi la briga di
+sottolineare in maniera esplicita che come per i file e le fifo, anche per i
+socket possono esserci più riferimenti contemporanei ad uno stesso socket. Per
+cui si avrebbe potuto avere l'impressione che sia una corrispondenza univoca
+fra un socket ed il file descriptor con cui vi si accede. Questo non è
+assolutamente vero, (e lo abbiamo già visto nel codice del server di
+\figref{fig:TCP_echo_server_first_code}), ed è invece assolutamente normale
+che, come per gli altri oggetti, ci possano essere più file descriptor che
+fanno riferimento allo stesso socket.
+
+Allora se avviene uno di questi casi quello che succederà è che la chiamata a
+\func{close} darà effettivamente avvio alla sequenza di chiusura di un socket
+soltanto quando il numero di riferimenti a quest'ultimo diventerà nullo.
+Fintanto che ci sono file descriptor che fanno riferimento ad un socket
+\func{close} si limiterà a deallocare nel processo corrente il file descriptor
+utilizzato, ma il socket resterà pienamente accessibile attraverso gli altri
+riferimenti.Se torniamo all'esempio di \figref{fig:TCP_echo_server_first_code}
+abbiamo infatti che le due \func{close} (sul socket connesso nel padre e sul
+socket in ascolto nel figlio), restando comunque altri riferimenti attivi (al
+socket connesso nel figlio e a quello in ascolto nel padre) non effettuano
+nessuna chiusura effettiva.  
+
+Questo non avviene affatto se si usa \func{shutdown} al posto di \func{close},
+in questo caso infatti la chiusura del socket viene effettuata immediatamente,
+indipendentemente dalla presenza di altri riferimenti attivi, e pertanto sarà
+ovviamente efficace anche per tutti gli altri file descriptor con cui si fa
+riferimento allo stesso socket.
+
+Il caso più comune di uso di \func{shutdown} è comunque quello della chiusura
+del lato in scrittura, per segnalare all'altro capo della connessione che si è
+concluso l'invio dei dati, restando comunque in grado di ricevere quanto
+ancora questi potrà inviarci. Questo è ad esempio l'uso che ci serve per
+rendere finalmente completo il nostro esempio sul servizio echo. Il nostro
+client infatti presenta ancora un problema, che nell'uso che finora ne abbiamo
+fatto non è emerso, ma che ci aspetta dietro l'angolo non appena usciamo
+dall'uso interattivo e proviamo ad eseguirlo redirigendo standard input e
+standard output. Così se eseguiamo:
+\begin{verbatim}
+[piccardi@gont sources]$ ./echo 192.168.1.1 < ../fileadv.tex  > copia
+\end{verbatim}%$
+vedremo che il file \texttt{copia} risulta mancare della parte finale.
+
+Per capire cosa avviene in questo caso occorre tenere presente come avviene la
+comunicazione via rete; quando redirigiamo lo standard input il nostro client
+inizierà a leggere il contenuto del file \texttt{../fileadv.tex} a blocchi di
+dimensione massima pari a \texttt{MAXLINE} per poi scriverlo, alla massima
+velocità consentitagli dalla rete, sul socket. Dato che la connessione è con
+una macchina remota occorre un certo tempo perché i pacchetti vi arrivino,
+vengano processati, e poi tornino indietro. Considerando trascurabile il tempo
+di processo, questo tempo, detto RTT (da \textit{Round Trip Time} può essere
+stimato con l'uso del comando \cmd{ping}. Ma mantre il pacchetti sono in
+transito sulla rete il client continua a leggere e a scrivere fintanto che il
+file in ingresso finisce. 
+
+A questo punto, se torniamo al codice mostrato in
+\figref{fig:TCP_ClientEcho_third}, notiamo che non appena viene ricevuto un
+end-of-file in ingresso il nostro client termina. Nel caso interattivo, in cui
+si inviavano brevi stringe una alla volta, c'era sempre il tempo di eseguire
+la lettura completa di quanto il server rimandava indietro. In questo caso
+però quando il client termina, essendo la comunicazione a piena velocità, ci
+saranno ancora pacchetti in transito sulla rete, ma siccome il client esce
+immediatamente dopo la fine del file in ingresso, questi non faranno a tempo a
+completare il percorso e verranno persi.
+
+Per evitare questo tipo di problema occorre, invece di uscire, usare
+\func{shutdown} per effettuare la chiusura del socket in scrittura una volta
+completata la lettura del file in ingresso. In questo modo il client segnalerà
+al server la chiusura del flusso dei dati, ma potrà continuare a leggere
+quanto il server gli sta ancora inviando fino a quando quest'ultimo,
+riconosciuta la chiusura del socket in scrittura da parte del client,
+effettuerà la chiusura dello stesso a sua volta. Solo alla ricezione della
+chiusura del socket da parte del server, si potrà essere sicuri della
+ricezione di tutti i dati prima della terminazione della connessione.
+
+\begin{figure}[!htb]
+  \footnotesize \centering
+  \begin{minipage}[c]{15.6cm}
+    \includecodesample{listati/ClientEcho.c}
+  \end{minipage} 
+  \normalsize
+  \caption{La sezione nel codice della versione finale della funzione
+    \func{ClientEcho}, che usa \func{shutdown} per una conclusione corretta
+    della connessione.}
+  \label{fig:TCP_ClientEcho}
+\end{figure}
+
+Si è allora riportato in \figref{fig:TCP_ClientEcho} la versione finale della
+nostra funzione \func{ClientEcho}, in grado di gestire correttamente l'intero
+flusso di dati fra client e server. Il codice completo del client,
+comprendente la gestione delle opzioni a riga di comando e le istruzioni per
+la creazione della connessione, si trova nel file \file{TCP\_echo.c},
+distribuito coi sorgenti allegati alla guida.
+
+La nuova versione è molto simile alla precedente di
+\figref{fig:TCP_ClientEcho_third}; la prima differenza è l'introduzione
+(\texttt{\small 7}) della variabile \var{eof}, inizializzata ad un valore
+nullo, che serve a mantenere traccia dell'avvenuta conclusione della lettura
+del file in ingresso.
+
+La seconda modifica (\texttt{\small 12--15}) è stata quella di rendere
+subordinato ad un valore nullo di \var{eof} l'impostazione del file descriptor
+set per l'osservazione dello standard input. Se infatti il valore di \var{eof}
+è non nullo significa che si è già raggiunta la fine del file in ingresso ed è
+pertanto inutile continuare a tenere sotto controllo lo standard input nella
+successiva (\texttt{\small 16}) chiamata a \func{select}.
+
+Le maggiori modifiche rispetto alla precedente versione sono invece nella
+gestione (\texttt{\small 18--22}) del caso in cui la lettura con \func{fgets}
+restitisca un valore nullo, indice della fine del file, che prima causava
+l'immediato ritorno della funzione. In questo caso prima (\texttt{\small 19})
+si imposta opportunamente \var{eof} ad un valore non nullo, dopo di che
+(\texttt{\small 20}) si effettua la chiusura del lato in scrittura del socket
+con \func{shutdown}. Infine (\texttt{\small 21}) si usa la macro
+\macro{FD\_CLR} per togliere lo standard input dal file descriptor set.
+
+In questo modo anche se la lettura del file in ingresso è conclusa, la
+funzione non esce dal ciclo principale (\texttt{\small 11--50}), ma continua
+ad eseguirlo ripetendo la chiamata a \func{select} per tenere sotto controllo
+soltanto il socket connesso, dal quale possono arrivare altri dati, che
+saranno letti (\texttt{\small 31}), ed opportunamente trascritti
+(\texttt{\small 44--48}) sullo standard input.
+
+Il ritorno della funzione, e la conseguente terminazione normale del client,
+viene invece adesso gestito all'interno (\texttt{\small 30--49}) della lettura
+dei dati dal socket; se infatti dalla lettura del socket si riceve una
+condizione di end-of-file, la si tratterà (\texttt{\small 36--43}) in maniera
+diversa a seconda del valore di \var{eof}. Se infatti questa è diversa da zero
+(\texttt{\small 37--39}), essendo stata completata la lettura del file in
+ingresso, vorrà dire che anche il server ha concluso la trasmissione dei dati
+restanti, e si potrà uscire senza errori, altrimenti si stamperà
+(\texttt{\small 40--42}) un messaggio di errore per la chiusura precoce della
+connesione.