X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=tcpsockadv.tex;h=d2e58fe5c0e123eaf7d4379458442e8679fef3f1;hp=e77e4320ec8878aad850118fa9bf870e1b8f3725;hb=af65119453850f0d2f50d38769bada5bcb8b729d;hpb=4b5c12ae4a519b8282fd3a6dcdbc33ee7dd8c3c7 diff --git a/tcpsockadv.tex b/tcpsockadv.tex index e77e432..d2e58fe 100644 --- a/tcpsockadv.tex +++ b/tcpsockadv.tex @@ -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,8 +322,223 @@ 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 l'uso +di \func{close} si limiterà a deallocare nel processo corrente il file +descriptor utilizzato, ma il socket resterà pienamente accessibile attraverso +tutti gli altri riferimenti. Se torniamo all'esempio originale del server di +\figref{fig:TCP_echo_server_first_code} abbiamo infatti che ci sono due +\func{close}, una sul socket connesso nel padre, ed una sul socket in ascolto +nel figlio, ma queste non effettuano nessuna chiusura reale di detti socket, +dato che restano altri riferimenti attivi, uno al socket connesso nel figlio +ed uno a quello in ascolto nel padre. + +Questo non avviene affatto se si usa \func{shutdown} con argomento +\macro{SHUT\_RDWR} 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à efficace anche per tutti +gli altri file descriptor con cui, nello stesso o in altri processi, 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 +questi potrà ancora inviarci. Questo è ad esempio l'uso che ci serve per +rendere finalmente completo il nostro esempio sul servizio \textit{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 è quello impiegato nella trasmissione via rete, che +viene detto RTT (da \textit{Round Trip Time}, e può essere stimato con l'uso +del comando \cmd{ping}. + +A questo punto, se torniamo al codice mostrato in +\figref{fig:TCP_ClientEcho_third}, possiamo vedere che mentre i pacchetti sono +in transito sulla rete il client continua a leggere e a scrivere fintanto che +il file in ingresso finisce. Però che non appena viene ricevuto un end-of-file +in ingresso il nostro client termina. Nel caso interattivo, in cui si +inviavano brevi stringhe una alla volta, c'era sempre il tempo di eseguire la +lettura completa di quanto il server rimandava indietro. In questo caso +invece, quando il client termina, essendo la comunicazione saturata e a piena +velocità, ci saranno ancora pacchetti in transito sulla rete che devono +arrivare al server e poi tornare indietro, 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, invece di uscire occorre usare +\func{shutdown} per effettuare la chiusura del lato in scrittura del socket, +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 indietro fino a quando anche +lui, riconosciuta la chiusura del socket in scrittura da parte del client, +effettuerà la chiusura dalla sua parte. Solo alla ricezione della chiusura del +socket da parte del server si potrà essere sicuri della ricezione di tutti i +dati e della terminazione effettiva 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} +restituisce un valore nullo, indice della fine del file. Questa nella +precedente versione 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 output. + +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 +connessione. + + +\subsection{Un server basato sull'I/O multiplexing} +\label{sec:TCP_serv_select} + +Seguendo di nuovo le orme di Stevens in \cite{UNP1} vediamo ora come con +l'utilizzo dell'I/O multiplexing diventi possibile riscrivere completamente il +nostro server \textit{echo} con una architettura completamente diversa, in +modo da evitare di dover creare un nuovo processo tutte le volte che si ha una +connessione. + +La struttura del nuovo server è illustrata in \figref{fig:TCP_echo_multiplex}, +in questo caso avremo un solo processo che ad ogni nuova connessione da parte +del client sul socket in ascolto inserirà il socket connesso ad essa relativo +in una opportuna tabella, poi utilizzerà \func{select} per rilevare la +presenza di dati in arrivo su ciascun socket connesso, riutilizzandolo per +inviare i dati in risposta. + + +\begin{figure}[htb] + \centering + \includegraphics[width=13cm]{img/TCPechoMult} + \caption{Schema del nuovo server echo basato sull'I/O multiplexing.} + \label{fig:TCP_echo_multiplex} +\end{figure} + + + + +\subsection{Un esempio di I/O multiplexing con \func{poll}} +\label{sec:TCP_serv_poll} +Abbiamo visto in \secref{sec:TCP_serv_select} come creare un server che +utilizzi l'I/O multiplexing attraverso l'impiego della funzione \func{select}, +ma in \secref{sec:file_multiplexing} abbiamo visto come la funzione +\func{poll} costituisca una alternativa a \func{select} con delle funzionalità +migliori, vediamo allora come reimplementare il nostro servizio usando questa +funzione. \section{Le opzioni dei socket}