X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=tcpsockadv.tex;h=991fe0494a2590eac0e2ade576fd81e167d1d7df;hp=af3348945068b2df47b195bb85105f81782a4e8f;hb=ed87bed5dc48e51b22e087d9daf411b430f8c536;hpb=af73295172df06a6a91edc4c206b6e5633c566f6 diff --git a/tcpsockadv.tex b/tcpsockadv.tex index af33489..991fe04 100644 --- a/tcpsockadv.tex +++ b/tcpsockadv.tex @@ -1,4 +1,4 @@ - %% tcpsockadv.tex +%% tcpsockadv.tex %% %% Copyright (C) 2003 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free @@ -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 @@ -238,24 +240,65 @@ connesso, ed alla ricezione del FIN la funzione \func{select} ritorner illustrato in \secref{sec:TCP_sock_select}) segnalando una condizione di end of file, per cui il nostro client potrà uscire immediatamente. - - -\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} - +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}} @@ -279,7 +322,535 @@ 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 (dalla denominazione inglese \textit{Round Trip Time}) ed è +quello che viene 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ò 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 una volta completata la +lettura del file in ingresso, occorre usare \func{shutdown} per effettuare la +chiusura del lato in scrittura del socket. 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 anch'esso, +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 il client potrà essere sicuro 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.\footnote{ne faremo comunque una implementazione diversa rispetto + a quella presentata da Stevens in \cite{UNP1}.} + +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 +di un client sul socket in ascolto si limiterà a registrare l'entrata in uso +di un nuovo file descriptor ed utilizzerà \func{select} per rilevare la +presenza di dati in arrivo su tutti i file descriptor attivi, operando +direttamente su ciascuno di essi. + +\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} + +La sezione principale del codice del nuovo server è illustrata in +\figref{fig:TCP_SelectEchod}. Si è tralasciata al solito la gestione delle +opzioni, che è identica alla versione precedente. Resta invariata anche tutta +la parte relativa alla gestione dei segnali, degli errori, e della cessione +dei privilegi, così come è identica la gestione della creazione del socket (si +può fare riferimento al codice già illustrato in +\secref{sec:TCPsimp_server_main}); al solito il codice completo del server è +disponibile coi sorgenti allegati nel file \texttt{select\_echod.c}. + +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/select_echod.c} + \end{minipage} + \normalsize + \caption{La sezione principale del codice della nuova versione di server + \textit{echo} basati sull'uso della funzione \func{select}.} + \label{fig:TCP_SelectEchod} +\end{figure} + +In questo caso, una volta aperto e messo in ascolto il socket, tutto quello +che ci servirà sarà chiamare \func{select} per rilevare la presenza di nuove +connessioni o di dati in arrivo, e processarli immediatamente. Per +implementare lo schema mostrato in \figref{fig:TCP_echo_multiplex}, il +programma usa una tabella dei socket connessi mantenuta nel vettore +\var{fd\_open} dimensionato al valore di \macro{FD\_SETSIZE}, ed una variabile +\var{max\_fd} per registrare il valore più alto dei file descriptor aperti. + +Prima di entrare nel ciclo principale (\texttt{\small 6--56}) la nostra +tabella viene inizializzata (\texttt{\small 2}) a zero (valore che +utilizzeremo come indicazione del fatto che il relativo file descriptor non è +aperto), mentre il valore massimo (\texttt{\small 3}) per i file descriptor +aperti viene impostato a quello del socket in ascolto,\footnote{in quanto esso + è l'unico file aperto, oltre i tre standard, e pertanto avrà il valore più + alto.} che verrà anche (\texttt{\small 4}) inserito nella tabella. + +La prima sezione (\texttt{\small 7--10}) del ciclo principale esegue la +costruzione del \textit{file descriptor set} \var{fset} in base ai socket +connessi in un certo momento; all'inizio ci sarà soltanto il socket in +ascolto, ma nel prosieguo delle operazioni, verranno utilizzati anche tutti i +socket connessi registrati nella tabella \var{fd\_open}. Dato che la chiamata +di \func{select} modifica il valore del \textit{file descriptor set}, è +necessario ripetere (\texttt{\small 7}) ogni volta il suo azzeramento, per poi +procedere con il ciclo (\texttt{\small 8--10}) in cui si impostano i socket +trovati attivi. + +Per far questo si usa la caratteristica dei file descriptor, descritta in +\secref{sec:file_open}, per cui il kernel associa sempre ad ogni nuovo file il +file descriptor con il valore più basso disponibile. Questo fa sì che si possa +eseguire il ciclo (\texttt{\small 8}) a partire da un valore minimo, che sarà +sempre quello del socket in ascolto, mantenuto in \var{list\_fd}, fino al +valore massimo di \var{max\_fd} che dovremo aver cura di tenere aggiornato. +Dopo di che basterà controllare (\texttt{\small 9}) nella nostra tabella se il +file descriptor è in uso o meno,\footnote{si tenga presente che benché il + kernel assegni sempre il primo valore libero, dato che nelle operazioni i + socket saranno aperti e chiusi in corrispondenza della creazione e + conclusione delle connessioni, si potranno sempre avere dei \textsl{buchi} + nella nostra tabella.} e impostare \var{fset} di conseguenza. + +Una volta inizializzato con i socket aperti il nostro \textit{file descriptor + set} potremo chiamare \func{select} per fargli osservare lo stato degli +stessi (in lettura, presumendo che la scrittura sia sempre consentita). Come +per il precedente esempio di \secref{sec:TCP_child_hand}, essendo questa +l'unica funzione che può bloccarsi, ed essere interrotta da un segnale, la +eseguiremo (\texttt{\small 11--12}) all'interno di un ciclo di \code{while} +che la ripete indefinitamente qualora esca con un errore di \errcode{EINTR}. +Nel caso invece di un errore normale si provvede (\texttt{\small 13--16}) ad +uscire stampando un messaggio di errore. + +Se invece la funzione ritorna normalmente avremo in \var{n} il numero di +socket da controllare. Nello specifico si danno due possibili casi diversi per +cui \func{select} può essere ritornata: o si è ricevuta una nuova connessione +ed è pronto il socket in ascolto, sul quale si può eseguire \func{accept} o +c'è attività su uno dei socket connessi, sui quali si può eseguire +\func{read}. + +Il primo caso viene trattato immediatamente (\texttt{\small 17--26}): si +controlla (\texttt{\small 17}) che il socket in ascolto sia fra quelli attivi, +nel qual caso anzitutto (\texttt{\small 18}) se ne decrementa il numero in +\var{n}; poi, inizializzata (\texttt{\small 19}) la lunghezza della struttura +degli indirizzi, si esegue \func{accept} per ottenere il nuovo socket connesso +controllando che non ci siano errori (\texttt{\small 20--23}). In questo caso +non c'è più la necessità di controllare per interruzioni dovute a segnali, in +quanto siamo sicuri che \func{accept} non si bloccherà. Per completare la +trattazione occorre a questo punto aggiungere (\texttt{\small 24}) il nuovo +file descriptor alla tabella di quelli connessi, ed inoltre, se è il caso, +aggiornare (\texttt{\small 25}) il valore massimo in \var{max\_fd}. + +Una volta controllato l'arrivo di nuove connessioni si passa a verificare se +vi sono dati sui socket connessi, per questo si ripete un ciclo +(\texttt{\small 29--55}) fintanto che il numero di socket attivi \var{n} resta +diverso da zero; in questo modo se l'unico socket con attività era quello +connesso, avendo opportunamente decrementato il contatore, il ciclo verrà +saltato, e si ritornerà immediatamente (ripetuta l'inizializzazione del file +descriptor set con i nuovi valori nella tabella) alla chiamata di +\func{accept}. Se il socket attivo non è quello in ascolto, o ce ne sono +comunque anche altri, il valore di \var{n} non sarà nullo ed il controllo sarà +eseguito. Prima di entrare nel ciclo comunque si inizializza (\texttt{\small + 28}) il valore della variabile \var{i} che useremo come indice nella tabella +\var{fd\_open} al valore minimo, corrispondente al file descriptor del socket +in ascolto. + +Il primo passo (\texttt{\small 30}) nella verifica è incrementare il valore +dell'indice \var{i} per posizionarsi sul primo valore possibile per un file +descriptor associato ad un eventuale socket connesso, dopo di che si controlla +(\texttt{\small 31}) se questo è nella tabella dei socket connessi, chiedendo +la ripetizione del ciclo in caso contrario. Altrimenti si passa a verificare +(\texttt{\small 32}) se il file descriptor corrisponde ad uno di quelli +attivi, e nel caso si esegue (\texttt{\small 33}) una lettura, uscendo con un +messaggio in caso di errore (\texttt{\small 34--38}). + +Se (\texttt{\small 39}) il numero di byte letti \var{nread} è nullo si è in +presenza del caso di un \textit{end-of-file}, indice che una connessione che +si è chiusa, che deve essere trattato (\texttt{\small 39--48}) opportunamente. +Il primo passo è chiudere (\texttt{\small 40}) anche il proprio capo del +socket e rimuovere (\texttt{\small 41}) il file descriptor dalla tabella di +quelli aperti, inoltre occorre verificare (\texttt{\small 42}) se il file +descriptor chiuso è quello con il valore più alto, nel qual caso occorre +trovare (\texttt{\small 42--46}) il nuovo massimo, altrimenti (\texttt{\small + 47}) si può ripetere il ciclo da capo per esaminare (se ne restano) +ulteriori file descriptor attivi. + +Se però è stato chiuso il file descriptor più alto, dato che la scansione dei +file descriptor attivi viene fatta a partire dal valore più basso, questo +significa che siamo anche arrivati alla fine della scansione, per questo +possiamo utilizzare direttamente il valore dell'indice \var{i} con un ciclo +all'indietro (\texttt{\small 43}) che trova il primo valore per cui la tabella +presenta un file descriptor aperto, e lo imposta (\texttt{\small 44}) come +nuovo massimo, per poi tornare (\texttt{\small 44}) al ciclo principale con un +\code{break}, e rieseguire \func{select}. + +Se infine si sono effettivamente letti dei dati dal socket (ultimo caso +rimasto) si potrà invocare immediatamente (\texttt{\small 49}) +\func{FullWrite} per riscriverli indietro sul socket stesso, avendo cura di +uscire con un messaggio in caso di errore (\texttt{\small 50--53}). Si noti +che nel ciclo si esegue una sola lettura, contrariamente a quanto fatto con la +precedente versione (si riveda il codice di \secref{fig:TCP_ServEcho_second}) +in cui si continuava a leggere fintanto che non si riceveva un +\textit{end-of-file}, questo perché usando l'\textit{I/O multiplexing} non si +vuole essere bloccati in lettura. L'uso di \func{select} ci permette di +trattare automaticamente anche il caso in cui la \func{read} non è stata in +grado di leggere tutti i dati presenti sul socket, dato che alla iterazione +successiva \func{select} ritornerà immediatamente segnalando l'ulteriore +disponibilità. + +Il nostro server comunque soffre di una vulnerabilità per un attacco di tipo +\textit{Denial of Service}. Il problema è che in caso di blocco di una +qualunque delle funzioni di I/O, non avendo usato processi separati, tutto il +server si ferma e non risponde più a nessuna richiesta. Abbiamo scongiurato +questa evenienza per l'I/O in ingresso con l'uso di \func{select}, ma non vale +altrettanto per l'I/O in uscita. Il problema pertanto può sorgere qualora una +delle chiamate a \func{write} effettuate da \func{FullWrite} si blocchi. Con +il funzionamento normale questo non accade in quanto il server si limita a +scrivere quanto riceve in ingresso, ma qualora venga utilizzato un client +malevolo che esegua solo scritture e non legga mai indietro l'\textsl{eco} del +server, si potrebbe giungere alla saturazione del buffer di scrittura, ed al +conseguente blocco del server su di una \func{write}. + +Le possibili soluzioni in questo caso sono quelle di ritornare ad eseguire il +ciclo di risposta alle richieste all'interno di processi separati, utilizzare +un timeout per le operazioni di scrittura, o eseguire queste ultime in +modalità non bloccante, concludendo le operazioni qualora non vadano a buon +fine. + + + +\subsection{I/O multiplexing con \func{poll}} +\label{sec:TCP_serv_poll} + +Finora abbiamo trattato le problematiche risolubili con l'I/O multiplexing +impiegando la funzione \func{select}; questo è quello che avviene nella +maggior parte dei casi, in quanto essa è nata sotto BSD proprio per affrontare +queste problematiche con i socket. Abbiamo però visto in +\secref{sec:file_multiplexing} come la funzione \func{poll} possa costituire +una alternativa a \func{select}, con alcuni vantaggi.\footnote{non soffrendo + delle limitazioni dovute all'uso dei \textit{file descriptor set}.} + +Ancora una volta in \secref{sec:file_poll} abbiamo trattato la funzione in +maniera generica, parlando di file descriptor, ma come per \func{select} +quando si ha a che fare con dei socket il concetto di essere \textsl{pronti} +per l'I/O deve essere specificato nei dettagli, per tener conto delle +condizioni della rete. Inoltre deve essere specificato come viene classificato +il traffico nella suddivisione fra dati normali e prioritari. In generale +pertanto: +\begin{itemize} +\item i dati trasmessi su un socket vengono considerati traffico normale, + pertanto vengono rilevati da una selezione con \const{POLLIN} o + \const{POLLRDNORM}. +\item i dati \textit{out-of-band} su un socket TCP vengono considerati + traffico prioritario e vengono rilevati da una condizione \const{POLLIN}, + \const{POLLPRI} o \const{POLLRDBAND}. +\item la chiusura di una connessione (cioè la ricezione di un segmento FIN) + viene considerato traffico normale, pertanto viene rilevato da una + condizione \const{POLLIN} o \const{POLLRDNORM}, ma una conseguente chiamata + a \func{read} restituirà 0. +\item la presenza di un errore sul socket (sia dovuta ad un segmento RST che a + timeout) viene considerata traffico normale, ma viene segnalata anche dalla + condizione \const{POLLERR}. +\item la presenza di una nuova connessione su un socket in ascolto può essere + considerata sia traffico normale che prioritario, nel caso di Linux + l'implementazione la classifica come normale. +\end{itemize} + +Come esempio dell'uso di \func{poll} proviamo allora a reimplementare il +server \textit{echo} secondo lo schema di \figref{fig:TCP_echo_multiplex} +usando \func{poll} al posto di \func{select}. In questo caso dovremo fare +qualche modifica, per tenere conto della diversa sintassi delle due funzioni, +ma la struttura del programma resta sostanzialmente la stessa. + + +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/poll_echod.c} + \end{minipage} + \normalsize + \caption{La sezione principale del codice della nuova versione di server + \textit{echo} basati sull'uso della funzione \func{poll}.} + \label{fig:TCP_PollEchod} +\end{figure} + +In \figref{fig:TCP_PollEchod} è riportata la sezione principale della nuova +versione del server, la versione completa del codice è riportata nel file +\file{poll\_echod.c} dei sorgenti allegati alla guida. Al solito nella figura +si sono tralasciate la gestione delle opzioni, la creazione del socket in +ascolto, la cessione dei privilegi e le operazioni necessarie a far funzionare +il programma come demone, privilegiando la sezione principale del programma. + +Come per il precedente server basato su \func{select} il primo passo +(\texttt{\small 2--8}) è quello di inizializzare le variabili necessarie. Dato +che in questo caso dovremo usare un vettore di strutture occorre anzitutto +(\texttt{\small 2}) allocare la memoria necessaria utilizzando il numero +massimo \var{n} di socket osservabili, che viene impostato attraverso +l'opzione \texttt{-n} ed ha un valore di default di 256. + +Dopo di che si preimposta (\texttt{\small 3}) il valore \var{max\_fd} del file +descriptor aperto con valore più alto a quello del socket in ascolto (al +momento l'unico), e si provvede (\texttt{\small 4--7}) ad inizializzare le +strutture, disabilitando (\texttt{\small 5}) l'osservazione con un valore +negativo del campo \var{fd} ma predisponendo (\texttt{\small 6}) il campo +\var{events} per l'osservazione dei dati normali con \const{POLLRDNORM}. +Infine (\texttt{\small 8}) si attiva l'osservazione del socket in ascolto +inizializzando la corrispondente struttura. Questo metodo comporta, in +modalità interattiva, lo spreco di tre strutture (quelle relative a standard +input, output ed error) che non vengono mai utilizzate in quanto la prima è +sempre quella relativa al socket in ascolto. + +Una volta completata l'inizializzazione tutto il lavoro viene svolto +all'interno del ciclo principale \texttt{\small 10--55}) che ha una struttura +sostanzialmente identica a quello usato per il precedente esempio basato su +\func{select}. La prima istruzione (\texttt{\small 11--12}) è quella di +eseguire \func{poll} all'interno di un ciclo che la ripete qualora venisse +interrotta da un segnale, da cui si esce soltanto quando la funzione ritorna, +restituendo nella variabile \var{n} il numero di file descriptor trovati +attivi. Qualora invece si sia ottenuto un errore si procede (\texttt{\small + 13--16}) alla terminazione immediata del processo provvedendo a stampare una +descrizione dello stesso. + +Una volta ottenuta dell'attività su un file descriptor si hanno di nuovo due +possibilità. La prima possibilità è che ci sia attività sul socket in ascolto, +indice di una nuova connessione, nel qual caso si controlla (\texttt{\small + 17}) se il campo \var{revents} della relativa struttura è attivo; se è così +si provvede (\texttt{\small 18}) a decrementare la variabile \var{n} (che +assume il significato di numero di file descriptor attivi rimasti da +controllare) per poi (\texttt{\small 19--23}) effettuare la chiamata ad +\func{accept}, terminando il processo in caso di errore. Se la chiamata ad +\func{accept} ha successo si procede attivando (\texttt{\small 24}) la +struttura relativa al nuovo file descriptor da essa ottenuto, modificando +(\texttt{\small 24}) infine quando necessario il valore massimo dei file +descriptor aperti mantenuto in \var{max\_fd}. + +La seconda possibilità è che vi sia dell'attività su uno dei socket aperti in +precedenza, nel qual caso si inizializza (\texttt{\small 27}) l'indice \var{i} +del vettore delle strutture \struct{pollfd} al valore del socket in ascolto, +dato che gli ulteriori socket aperti avranno comunque un valore superiore. Il +ciclo (\texttt{\small 28--54}) prosegue fintanto che il numero di file +descriptor attivi, mantenuto nella variabile \var{n}, è diverso da zero. Se +pertanto ci sono ancora socket attivi da individuare si comincia con +l'incrementare (\texttt{\small 30}) l'indice e controllare (\texttt{\small + 31}) se corrisponde ad un file descriptor in uso analizzando il valore del +campo \var{fd} della relativa struttura e chiudendo immediatamente il ciclo +qualora non lo sia. Se invece il file descriptor è in uso si verifica +(\texttt{\small 31}) se c'è stata attività controllando il campo +\var{revents}. + +Di nuovo se non si verifica la presenza di attività il ciclo si chiude subito, +altrimenti si provvederà (\texttt{\small 32}) a decrementare il numero \var{n} +di file descriptor attivi da controllare e ad eseguire (\texttt{\small 33}) la +lettura, ed in caso di errore (\texttt{\small 34--37}) al solito lo si +notificherà uscendo immediatamente. Qualora invece si ottenga una condizione +di end-of-file (\texttt{\small 38--47}) si provvederà a chiudere +(\texttt{\small 39}) anche il nostro capo del socket e a marcarlo +(\texttt{\small 40}) nella struttura ad esso associata come inutilizzato. +Infine dovrà essere ricalcolato (\texttt{\small 41--45}) un eventiale nuovo +valore di \var{max\_fd}. L'ultimo passo è (\texttt{\small 46}) chiudere il +ciclo in quanto in questo caso non c'è più niente da riscrivere all'indietro +sul socket. + +Se invece si sono letti dei dati si provvede (\texttt{\small 48}) ad +effettuarne la riscrittura all'indietro, con il solito controllo ed eventuale +uscita e notifica in caso si errore (\texttt{\small 49--52}). + +Come si può notare la logica del programma è identica a quella vista in +\figref{fig:TCP_SelectEchod} per l'analogo server basato su \func{select}; la +sola differenza significativa è che in questo caso non c'è bisogno di +rigenerare i file descriptor set in quanto l'uscita è indipendente dai dati in +ingresso. + + +\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}