Versione finale del client ECHO su TCP, con esempio di uso della funzione
[gapil.git] / tcpsockadv.tex
index 44986525fc3141b95422a22548adb4ede34b939a..d22c6aa84ab43a5696de32e2346915e11a11424c 100644 (file)
@@ -182,12 +182,14 @@ 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
 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}.
-
-In questo caso la funzione comincia (\texttt{\small 8--9}) con la
-cancellazione del file descriptor set \var{fset} e del valore \var{maxfd} da
-passare a \func{select} come massimo per il numero dei file descriptor. Per
-quest'ultimo si usa la macro \code{max} definita nel nostro file
+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
+passare a \func{select} come massimo per il numero di file descriptor. Per
+determinare quest'ultimo si usa la macro \code{max} definita nel nostro file
 \file{macro.h} che raccoglie una collezione di macro di preprocessore di varia
 utilità.
 
 \file{macro.h} che raccoglie una collezione di macro di preprocessore di varia
 utilità.
 
@@ -197,47 +199,106 @@ viene ripetuto indefinitamente. Per ogni ciclo si reinizializza
 file descriptor associato al socket \var{socket} e per lo standard input (il
 cui valore si recupera con la funzione \func{fileno}). Questo è necessario in
 quanto la successiva (\texttt{\small 13}) chiamata a \func{select} comporta
 file descriptor associato al socket \var{socket} e per lo standard input (il
 cui valore si recupera con la funzione \func{fileno}). Questo è necessario in
 quanto la successiva (\texttt{\small 13}) chiamata a \func{select} comporta
-una modifica dei due bit relativi, che quindi devono essere reimpostati.
+una modifica dei due bit relativi, che quindi devono essere reimpostati
+all'inizio di ogni ciclo.
 
 Si noti come la chiamata a \func{select} venga eseguita usando come primo
 
 Si noti come la chiamata a \func{select} venga eseguita usando come primo
-argomento il valore di \var{maxfd}, precedentemente calcolato, passando poi il
-solo file descriptor set per il controllo dell'attività in lettura, gli altri
-argomenti sono tutti passati come puntatori nulli non interessando né il
+argomento il valore di \var{maxfd}, precedentemente calcolato, e passando poi
+il solo file descriptor set per il controllo dell'attività in lettura, negli
+altri argomenti sono passati tutti puntatori nulli, non interessando né il
 controllo delle altre attività, né l'impostazione di un valore di timeout.
 
 controllo delle altre attività, né l'impostazione di un valore di timeout.
 
-Al ritorno di \func{select} si provvede a controllare quale dei file
-descriptor presneta attività, si comincia (\texttt{\small 14--24}) con il file
-descriptor associato allo standard input. In caso di attività (quando cioè
-\macro{FD_ISSET} ritorna una valore diverso da zero) si esegue (\texttt{\small
-  15}) una \func{fgets} per leggere gli eventuali dati presenti; se non ve ne
-sono (e la funzione restituisce pertanto un puntatore nullo) si ritorna
-immediatamente (\texttt{\small 16}) dato che questo significa che si è chiuso
-lo standard input; altrimenti (\texttt{\small 18--22}) si scrivono i dati sul
-socket, uscendo immediatamente in caso di errore di scrittura.
+Al ritorno di \func{select} si provvede a controllare quale dei due file
+descriptor presenta attività in lettura, cominciando (\texttt{\small 14--24})
+con il file descriptor associato allo standard input. In caso di attività
+(quando cioè \macro{FD\_ISSET} ritorna una valore diverso da zero) si esegue
+(\texttt{\small 15}) una \func{fgets} per leggere gli eventuali dati presenti;
+se non ve ne sono (e la funzione restituisce pertanto un puntatore nullo) si
+ritorna immediatamente (\texttt{\small 16}) dato che questo significa che si è
+chiuso lo standard input e quindi concluso l'utilizzo del client; altrimenti
+(\texttt{\small 18--22}) si scrivono i dati appena letti sul socket,
+prevedendo una uscita immediata in caso di errore di scrittura.
 
 Controllato lo standard input si passa a controllare (\texttt{\small 25--40})
 
 Controllato lo standard input si passa a controllare (\texttt{\small 25--40})
-il socket connesso, in caso di attività si esegue (\texttt{\small 26}) subito
+il socket connesso, in caso di attività (\texttt{\small 26}) si esegue subito
 una \func{read} di cui si controlla il valore di ritorno; se questo è negativo
 (\texttt{\small 27--30}) si è avuto un errore e pertanto si esce
 una \func{read} di cui si controlla il valore di ritorno; se questo è negativo
 (\texttt{\small 27--30}) si è avuto un errore e pertanto si esce
-immediatamente segnalandolo, se è nullo (\texttt{\small 31--34}) 
-
-
-\section{Le opzioni dei socket}
-\label{sec:TCP_sock_options}
-
-Dato che la maggior parte delle opzioni dei socket sono relative ai socket
-TCP, ed hanno poi significato analogo quando usate con altri socket, abbiamo
-preferito trattare l'argomento in generale in questa sezione piuttosto che nel
-capitolo dedicato alla trattazione generica dei socket.
-
-
-
-\section{I dati \textit{out-of-band}}
-\label{sec:TCP_urgent_data}
-
-Una caratteristica speciale dei socket TCP è quella della presenza dei
-cosiddetti dati \textit{out-of-band}
-
+immediatamente segnalandolo, se è nullo (\texttt{\small 31--34}) significa che
+il server ha chiuso la connessione, e di nuovo si esce con stampando prima un
+messaggio di avviso, altrimenti (\texttt{\small 35--39}) si effettua la
+terminazione della stringa e la si stampa a sullo standard output (uscendo in
+caso di errore), per ripetere il ciclo da capo.
+
+Con questo meccanismo il programma invece di essere bloccato in lettura sullo
+standard input resta bloccato sulla \func{select}, che ritorna soltanto quando
+viene rilevata attività su uno dei due file descriptor posti sotto controllo.
+Questo di norma avviene solo quando si è scritto qualcosa sullo standard
+input, o quando si riceve dal socket la risposta a quanto si era appena
+scritto. Ma adesso il client diventa capace di accorgersi immediatamente della
+terminazione del server; in tal caso infatti il server chiuderà il socket
+connesso, ed alla ricezione del FIN la funzione \func{select} ritornerà (come
+illustrato in \secref{sec:TCP_sock_select}) segnalando una condizione di end
+of file, per cui il nostro client potrà uscire immediatamente.
+
+Riprendiamo la situazione affrontata in \secref{sec:TCP_server_crash},
+terminando il server durante una connessione, in questo caso quello che
+otterremo, una volta scritta una prima riga ed interrotto il server con un
+\texttt{C-c}, sarà:
+\begin{verbatim}
+[piccardi@gont sources]$ ./echo 192.168.1.1
+Prima riga
+Prima riga
+EOF sul socket
+\end{verbatim}%$
+dove l'ultima riga compare immediatamente dopo aver interrotto il server. Il
+nostro client infatti è in grado di accorgersi immediatamente che il socket
+connesso è stato chiuso ed uscire immediatamente.
+
+Veniamo allora agli altri scenari di terminazione anomala visti in
+\secref{sec:TCP_conn_crash}. Il primo di questi è l'interruzione fisica della
+connessione; in questo caso avremo un comportamento analogo al precedente, in
+cui si scrive una riga e non si riceve risposta dal server e non succede
+niente fino a quando non si riceve un errore di \errcode{EHOSTUNREACH} o
+\errcode{ETIMEDOUT} a seconda dei casi.
+
+La differenza è che stavolta potremo scrivere più righe dopo l'interruzione,
+in quanto il nostro client dopo aver inviato i dati non si bloccherà più nella
+lettura dal socket, ma nella \func{select}; per questo potrà accettare
+ulteriore dati che scriverà di nuovo sul socket, fintanto che c'è spazio sul
+buffer di uscita (ecceduto il quale si bloccherà in scrittura). Si ricordi
+infatti che il client non ha modo di determinare se la connessione è attiva o
+meno (dato che in molte situazioni reali l'inattività può essere temporanea).
+Tra l'altro se si ricollega la rete prima della scadenza del timeout, potremo
+anche verificare come tutto quello che si era scritto viene poi effettivamente
+trasmesso non appena la connessione ridiventa attiva, per cui otterremo
+qualcosa del tipo:
+\begin{verbatim}
+[piccardi@gont sources]$ ./echo 192.168.1.1
+Prima riga
+Prima riga
+Seconda riga dopo l'interruzione
+Terza riga
+Quarta riga
+Seconda riga dopo l'interruzione
+Terza riga
+Quarta riga
+\end{verbatim}
+in cui, una volta riconnessa la rete, tutto quello che abbiamo scritto durante
+il periodo di disconnessione restituito indietro e stampato immediatamente.
+
+Lo stesso comportamento visto in \secref{sec:TCP_server_crash} si riottiene
+nel caso di un crollo completo della macchina su cui sta il server. In questo
+caso di nuovo il client non è in grado di accorgersi di niente dato che si
+suppone che il programma server non venga terminato correttamente, ma si
+blocchi tutto senza la possibilità di avere l'emissione di un segmento FIN che
+segnala la terminazione della connessione. Di nuovo fintanto che la
+connessione non si riattiva )con il riavvio della macchina del server) il
+client non è in grado di fare altro che accettare dell'input e tentare di
+inviarlo. La differenza in questo caso è che non appena la connessione
+ridiventa attiva i dati verranno sì trasmessi, ma essendo state perse tutte le
+informazioni relative alle precedenti connessioni ai tentativi di scrittura
+del client sarà risposto con un segmento RST che provocherà il ritorno di
+\func{select} per la ricezione di un errore di \errcode{ECONNRESET}.
 
 
 \subsection{La funzione \func{shutdown}}
 
 
 \subsection{La funzione \func{shutdown}}
@@ -261,7 +322,198 @@ 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
 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.
+
+
+
+\section{Le opzioni dei socket}
+\label{sec:TCP_sock_options}
+
+Dato che la maggior parte delle opzioni dei socket sono relative ai socket
+TCP, ed hanno poi significato analogo quando usate con altri socket, abbiamo
+preferito trattare l'argomento in generale in questa sezione piuttosto che nel
+capitolo dedicato alla trattazione generica dei socket.
+
+
+
+\section{I dati \textit{out-of-band}}
+\label{sec:TCP_urgent_data}
+
+Una caratteristica speciale dei socket TCP è quella della presenza dei
+cosiddetti dati \textit{out-of-band}