From: Simone Piccardi Date: Mon, 28 Apr 2003 14:13:50 +0000 (+0000) Subject: Ripulitura dei nomi e ristrutturazione dei capitoli. Tolto simpltcp che X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=commitdiff_plain;h=b324b7a09e071b2f84a1849d109d4d14f27f44cd;p=gapil.git Ripulitura dei nomi e ristrutturazione dei capitoli. Tolto simpltcp che converge dentro elemtcp. --- diff --git a/biblio.bib b/biblio.bib index e6f73b7..0f08eb5 100644 --- a/biblio.bib +++ b/biblio.bib @@ -30,18 +30,6 @@ OPTnote = {}, OPTannote = {} } -@Booklet{UnixFaq, - title = {Unix Programming Frequently Asked Questions}, - OPTkey = {}, - author = {Andrew Gierth}, - OPThowpublished = {http://www.erlenstar.demon.co.uk/unix/faq_toc.html}, - OPTaddress = {}, - OPTmonth = {}, - OPTyear = {}, - OPTnote = {}, - OPTannote = {} -} - @Book{UNP1, author = {W. Richard Stevens}, editor = {}, @@ -228,4 +216,14 @@ OPTnote = {}, OPTannote = {} } - +@Booklet{UnixFaq, + title = {Unix Programming Frequently Asked Questions}, + OPTkey = {}, + author = {Andrew Gierth}, + OPThowpublished = {http://www.erlenstar.demon.co.uk/unix/faq_toc.html}, + OPTaddress = {}, + OPTmonth = {}, + OPTyear = {}, + OPTnote = {}, + OPTannote = {} +} diff --git a/elemtcp.tex b/elemtcp.tex index 314a538..c57bbcf 100644 --- a/elemtcp.tex +++ b/elemtcp.tex @@ -8,7 +8,7 @@ %% license is included in the section entitled "GNU Free Documentation %% License". %% -\chapter{Socket TCP elementari} +\chapter{Socket TCP} \label{cha:elem_TCP_sock} In questo capitolo iniziamo ad approfondire la conoscenza dei socket TCP, @@ -161,7 +161,8 @@ regolare la connessione. Normalmente vengono usate le seguenti opzioni: \end{itemize} La MSS è generalmente supportata da quasi tutte le implementazioni del -protocollo, le ultime due opzioni (trattate nell'RFC~1323) sono meno comuni; +protocollo, le ultime due opzioni (trattate +nell'\href{http://www.ietf.org/rfc/rfc1323.txt}{RFC~1323}) sono meno comuni; vengono anche dette \textit{long fat pipe options} dato che questo è il nome che viene dato alle connessioni caratterizzate da alta velocità o da ritardi elevati. In ogni caso Linux supporta pienamente entrambe le opzioni. @@ -171,9 +172,9 @@ elevati. In ogni caso Linux supporta pienamente entrambe le opzioni. Mentre per creare una connessione occorre un interscambio di tre segmenti, la procedura di chiusura ne richiede quattro; ancora una volta si può fare -riferimento al codice degli esempi \figref{fig:TCP_cli_code} e -\figref{fig:TCP_serv_code}, in questo caso la successione degli eventi è la -seguente: +riferimento al codice degli esempi \figref{fig:TCP_daytime_client_code} e +\figref{fig:TCP_daytime_iter_server_code}, in questo caso la successione degli +eventi è la seguente: \begin{enumerate} \item Un processo ad uno dei due capi chiama la funzione \func{close}, dando @@ -226,19 +227,18 @@ come utilizzarlo in \secref{xxx_shutdown}, quando parleremo della funzione \func{shutdown}. La emissione del FIN avviene quando il socket viene chiuso, questo però non -avviene solo per la chiamata della funzione \func{close} (come faremo in -\figref{fig:TCP_serv_code}), ma anche alla terminazione di un processo (come -vedremo in \figref{fig:TCP_cli_code}). Questo vuol dire ad esempio che se un -processo viene terminato da un segnale tutte le connessioni aperte verranno -chiuse. +avviene solo per la chiamata esplicita della funzione \func{close}, ma anche +alla terminazione di un processo, quando tutti i file vengono chiusi. Questo +comporta ad esempio che se un processo viene terminato da un segnale tutte le +connessioni aperte verranno chiuse. Infine occorre sottolineare che, benché nella figura (e nell'esempio che vedremo più avanti in \secref{sec:TCPsimp_echo}) sia stato il client ad eseguire la chiusura attiva, nella realtà questa può essere eseguita da uno qualunque dei due capi della comunicazione (come nell'esempio di -\figref{fig:TCP_serv_code}), e anche se il caso più comune resta quello del -client, ci sono alcuni servizi, il principale dei quali è l'HTTP, per i quali -è il server ad effettuare la chiusura attiva. +\figref{fig:TCP_daytime_iter_server_code}), e anche se il caso più comune +resta quello del client, ci sono alcuni servizi, il principale dei quali è +l'HTTP, per i quali è il server ad effettuare la chiusura attiva. \subsection{Un esempio di connessione} @@ -348,10 +348,11 @@ pertanto anche se il TTL (da \textit{time to live}) non limite sul tempo di vita, si stima che un pacchetto IP non possa restare nella rete per più di MSL secondi. -Ogni implementazione del TCP deve scegliere un valore per la MSL (l'RFC~1122 -raccomanda 2 minuti, Linux usa 30 secondi), questo comporta una durata dello -stato \texttt{TIME\_WAIT} che a seconda delle implementazioni può variare fra -1 a 4 minuti. +Ogni implementazione del TCP deve scegliere un valore per la MSL +(l'\href{http://www.ietf.org/rfc/rfc1122.txt}{RFC~1122} raccomanda 2 minuti, +Linux usa 30 secondi), questo comporta una durata dello stato +\texttt{TIME\_WAIT} che a seconda delle implementazioni può variare fra 1 a 4 +minuti. Lo stato \texttt{TIME\_WAIT} viene utilizzato dal protocollo per due motivi principali: @@ -455,8 +456,9 @@ creazione della connessione. Queste sono dette effimere in quanto vengono usate solo per la durata della connessione, e l'unico requisito che deve essere soddisfatto è che ognuna di esse sia assegnata in maniera univoca. -La lista delle porte conosciute è definita dall'RFC~1700 che contiene l'elenco -delle porte assegnate dalla IANA (la \textit{Internet Assigned Number +La lista delle porte conosciute è definita +dall'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} che contiene +l'elenco delle porte assegnate dalla IANA (la \textit{Internet Assigned Number Authority}) ma l'elenco viene costantemente aggiornato e pubblicato su internet (una versione corrente si può trovare all'indirizzo \texttt{ftp://ftp.isi.edu/in-notes/iana/assignements/port-numbers}); inoltre @@ -483,8 +485,9 @@ nome simbolico del servizio. I numeri sono divisi in tre intervalli: sono i candidati naturali ad essere usate come porte effimere. \end{enumerate} -In realtà rispetto a quanto indicato nell'RFC~1700 i vari sistemi hanno fatto -scelte diverse per le porte effimere, in particolare in +In realtà rispetto a quanto indicato +nell'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} i vari sistemi hanno +fatto scelte diverse per le porte effimere, in particolare in \figref{fig:TCP_port_alloc} sono riportate quelle di BSD e Linux. Nel caso di Linux poi la scelta fra i due intervalli possibili viene fatta dinamicamente a seconda della memoria a disposizione del kernel per gestire le relative @@ -529,9 +532,10 @@ queste informazioni nei campi \textit{Local Address} e \textit{Foreing \label{sec:TCP_port_cliserv} Per capire meglio l'uso delle porte e come vengono utilizzate quando si ha a -che fare con un'applicazione client/server (come quella che scriveremo in -\secref{sec:TCP_cunc_daytime}) esamineremo cosa accade con le connessioni nel -caso di un server TCP che deve gestire connessioni multiple. +che fare con un'applicazione client/server (come quelle che scriveremo in +\secref{sec:TCP_daytime_application} e \secref{sec:TCP_echo_application}) +esamineremo cosa accade con le connessioni nel caso di un server TCP che deve +gestire connessioni multiple. Se eseguiamo un \cmd{netstat} su una macchina di prova (il cui indirizzo sia \texttt{195.110.112.152}) potremo avere un risultato del tipo: @@ -694,8 +698,8 @@ l'indirizzo di destinazione specificato dal SYN del client. Per specificare un indirizzo generico, con IPv4 si usa il valore \const{INADDR\_ANY}, il cui valore, come accennato in \secref{sec:sock_sa_ipv4}, è pari a zero; nell'esempio -\figref{fig:TCP_serv_code} si è usata un'assegnazione immediata del tipo: -\includecodesnip{listati/serv_addr_sin_addr.c} +\figref{fig:TCP_daytime_iter_server_code} si è usata un'assegnazione immediata +del tipo: \includecodesnip{listati/serv_addr_sin_addr.c} Si noti che si è usato \func{htonl} per assegnare il valore \const{INADDR\_ANY}, anche se, essendo questo nullo, il riordinamento è @@ -782,7 +786,7 @@ socket, gi La struttura dell'indirizzo deve essere inizializzata con l'indirizzo IP e il numero di porta del server a cui ci si vuole connettere, come mostrato -nell'esempio \secref{sec:TCP_cli_sample} usando le funzioni illustrate in +nell'esempio \secref{sec:TCP_daytime_client}, usando le funzioni illustrate in \secref{sec:sock_addr_func}. Nel caso di socket TCP la funzione \func{connect} avvia il \textit{three way @@ -1064,8 +1068,9 @@ nuovi socket, detti \textit{connected socket}, ritornati da \func{accept}, che si trovano automaticamente nello stato \texttt{ESTABLISHED}, e vengono utilizzati per lo scambio dei dati, che avviene su di essi, fino alla chiusura della connessione. Si può riconoscere questo schema anche nell'esempio -elementare di \figref{fig:TCP_serv_code}, dove per ogni connessione il socket -creato da \func{accept} viene chiuso dopo l'invio dei dati. +elementare di \figref{fig:TCP_daytime_iter_server_code}, dove per ogni +connessione il socket creato da \func{accept} viene chiuso dopo l'invio dei +dati. \subsection{Le funzioni \func{getsockname} e \func{getpeername}} @@ -1146,11 +1151,11 @@ che dal lato client l'indirizzo remoto Il fatto è che in generale quest'ultimo caso non è sempre possibile. In particolare questo avviene quando il server, invece di gestire la connessione direttamente in un processo figlio, come vedremo nell'esempio di server -concorrente di \ref{sec:TCP_cunc_daytime}, lancia per ciascuna connessione un -altro programma, usando \func{exec}.\footnote{questa ad esempio è la modalità - con cui opera il \textsl{super-server} \cmd{inetd}, che può gestire tutta - una serie di servizi diversi, eseguendo su ogni connessione ricevuta sulle - porte tenute sotto controllo, il relativo server.} +concorrente di \secref{sec:TCP_daytime_cunc_server}, lancia per ciascuna +connessione un altro programma, usando \func{exec}.\footnote{questa ad esempio + è la modalità con cui opera il \textsl{super-server} \cmd{inetd}, che può + gestire tutta una serie di servizi diversi, eseguendo su ogni connessione + ricevuta sulle porte tenute sotto controllo, il relativo server.} In questo caso benché il processo figlio abbia una immagine della memoria che è copia di quella del processo padre (e contiene quindi anche la struttura @@ -1202,16 +1207,19 @@ descritta in \secref{sec:TCP_conn_term}, si pu -\section{Un esempio di applicazione} -\label{sec:TCP_application} +\section{Un esempio elementare: il servizio \textit{daytime}} +\label{sec:TCP_daytime_application} Avendo introdotto le funzioni di base per la gestione dei socket, potremo -vedere in questa sezione un esempio di applicazione elementare. Prima di -passare agli esempi del client e del server, inizieremo riprendendo con -maggiori dettagli una caratteristica delle funzioni di I/O, già accennata in +vedere in questa sezione un primo esempio di applicazione elementare che +implementa il servizio \textit{daytime} su TCP, secondo quanto specificato +dall'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}. Prima di passare +agli esempi del client e del server, inizieremo riesaminando con maggiori +dettagli una peculiarità delle funzioni di I/O, già accennata in \secref{sec:file_read} e \secref{sec:file_write}, che nel caso dei socket è -particolarmente rilevante, per poi passare agli esempi, sia di server -iterativo, che di server concorrente. +particolarmente rilevante. Passeremo poi ad illustrare gli esempi +dell'implementazione, sia dal lato client, che dal lato server, che si è +realizzato sia in forma iterativa che concorrente. \subsection{Il comportamento delle funzioni di I/O} @@ -1229,12 +1237,6 @@ comportamento normale per l'I/O su file, ma con i normali file di dati il problema si avverte solo quando si incontra la fine del file. In generale non è così, e con i socket questo è particolarmente evidente. -Quando ci si trova ad affrontare questo comportamento tutto quello che si deve -fare è semplicemente ripetere la lettura (o la scrittura) per la quantità di -byte restanti, tenendo conto che le funzioni si possono bloccare se i dati non -sono disponibili: è lo stesso comportamento che si può avere scrivendo più di -\const{PIPE\_BUF} byte in una pipe (si riveda quanto detto in -\secref{sec:ipc_pipes}). \begin{figure}[htb] \footnotesize \centering @@ -1247,6 +1249,13 @@ sono disponibili: \label{fig:sock_FullRead_code} \end{figure} +Quando ci si trova ad affrontare questo comportamento tutto quello che si deve +fare è semplicemente ripetere la lettura (o la scrittura) per la quantità di +byte restanti, tenendo conto che le funzioni si possono bloccare se i dati non +sono disponibili: è lo stesso comportamento che si può avere scrivendo più di +\const{PIPE\_BUF} byte in una pipe (si riveda quanto detto in +\secref{sec:ipc_pipes}). + Per questo motivo, seguendo l'esempio di R. W. Stevens in \cite{UNP1}, si sono definite due funzioni, \func{FullRead} e \func{FullWrite}, che eseguono lettura e scrittura tenendo conto di questa caratteristica, ed in grado di @@ -1280,29 +1289,31 @@ l'altro capo pertanto si ritorna senza aver concluso la lettura di tutti i byte richiesti. -\subsection{Un primo esempio di client} -\label{sec:TCP_cli_sample} +\subsection{Il client \textit{daytime}} +\label{sec:TCP_daytime_client} -Il primo esempio di applicazione delle funzioni viste finora è relativo alla -creazione di un client elementare per il servizio \textit{daytime}, un -servizio standard che restituisce l'ora locale della macchina a cui si -effettua la richiesta, e che è assegnato alla porta 13. +Il primo esempio di applicazione delle funzioni di base illustrate in +precedenza è relativo alla creazione di un client elementare per il servizio +\textit{daytime}, un servizio elementare, definito +nell'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}, che restituisce +l'ora locale della macchina a cui si effettua la richiesta, e che è assegnato +alla porta 13. In \figref{fig:TCP_cli_code} è riportata la sezione principale del codice del -nostro client. Il sorgente completo del programma -(\file{ElemDaytimeTCPClient.c}, che comprende il trattamento delle opzioni ed -una funzione per stampare un messaggio di aiuto) è allegato alla guida nella -sezione dei codici sorgente e può essere compilato su una qualunque macchina -GNU/Linux. +nostro client. Il sorgente completo del programma (\file{TCP_daytime.c}, che +comprende il trattamento delle opzioni ed una funzione per stampare un +messaggio di aiuto) è allegato alla guida nella sezione dei codici sorgente e +può essere compilato su una qualunque macchina GNU/Linux. \begin{figure}[!htb] \footnotesize \centering \begin{minipage}[c]{15cm} - \includecodesample{listati/ElemDaytimeTCPClient.c} + \includecodesample{listati/TCP_daytime.c} \end{minipage} \normalsize - \caption{Esempio di codice di un client elementare per il servizio daytime.} - \label{fig:TCP_cli_code} + \caption{Esempio di codice di un client elementare per il servizio + \textit{daytime}.} + \label{fig:TCP_daytime_client_code} \end{figure} Il programma anzitutto (\texttt{\small 1--5}) include gli header necessari; @@ -1380,25 +1391,24 @@ Mon Apr 21 20:46:11 2003 e come si vede tutto funziona regolarmente. -\subsection{Un primo esempio di server} -\label{sec:TCP_serv_sample} +\subsection{Un server \textit{daytime} iterativo} +\label{sec:TCP_daytime_iter_server} Dopo aver illustrato il client daremo anche un esempio di un server elementare, che sia anche in grado di rispondere al precedente client. Come primo esempio realizzeremo un server iterativo, in grado di fornire una sola risposta alla volta. Il codice del programma è nuovamente mostrato in -\figref{fig:TCP_serv_code}, il sorgente completo -(\file{ElemDaytimeTCPServer.c}) è allegato insieme agli altri file degli -esempi. +\figref{fig:TCP_daytime_iter_server_code}, il sorgente completo +(\file{TCP_iter_daytimed.c}) è allegato insieme agli altri file degli esempi. \begin{figure}[!htbp] \footnotesize \centering \begin{minipage}[c]{15cm} - \includecodesample{listati/ElemDaytimeTCPServer.c} + \includecodesample{listati/TCP_iter_daytimed.c} \end{minipage} \normalsize \caption{Esempio di codice di un semplice server per il servizio daytime.} - \label{fig:TCP_serv_code} + \label{fig:TCP_daytime_iter_server_code} \end{figure} Come per il client si includono (\texttt{\small 1--9}) gli header necessari a @@ -1464,10 +1474,10 @@ vengono chiusi automaticamente alla sua uscita, e che, non generando figli, non è necessario preoccuparsi di gestire la loro terminazione. -\subsection{Un esempio di server \textit{daytime} concorrente} -\label{sec:TCP_cunc_daytime} +\subsection{Un server \textit{daytime} concorrente} +\label{sec:TCP_daytime_cunc_server} -Il server \texttt{daytime} dell'esempio in \secref{sec:TCP_cli_sample} è un +Il server \texttt{daytime} dell'esempio in \secref{sec:TCP_daytime_client} è un tipico esempio di server iterativo, in cui viene servita una richiesta alla volta; in generale però, specie se il servizio è più complesso e comporta uno scambio di dati più sostanzioso di quello in questione, non è opportuno @@ -1567,6 +1577,336 @@ torneremo su questo pi complessi. +\section{Un esempio più completo: il servizio \textit{echo}} +\label{sec:TCP_echo_application} + +L'esempio precedente, basato sul servizio \textit{daytime}, è un esempio molto +elementare, in cui il flusso dei dati va solo nella direzione dal server al +client. In questa sezione esamineremo un esempio di applicazione client/server +un po' più complessa, che usi i socket TCP per una comunicazione in entrambe +le direzioni, implementando il servizio standard \textit{echo}, così come +definito dall'\href{http://www.ietf.org/rfc/rfc0862.txt}{RFC~862}. + +Si è scelto di usare questo servizio, seguendo l'esempio di \cite{UNP1}, +perché costituisce il prototipo ideale di una generica applicazione di rete in +cui un server risponde alle richieste di un client; nel caso di una +applicazione più complessa si potrà avere in più una elaborazione dell'input +del client da parte del server nel fornire le risposte in uscita. + +Ci limiteremo ad un esempio elementare, che usi solo le funzioni di base, ma +prenderemo in esame, oltre al comportamento in condizioni normali, anche tutti +i possibili scenari particolari (errori, sconnessione della rete, crash del +client o del server durante la connessione) che possono avere luogo durante +l'impiego di un'applicazione di rete. + +Partiremo da un'implementazione elementare che dovrà essere rimaneggiata di +volta in volta per poter tenere conto di tutte le evenienze che si possono +manifestare nella vita reale di un'applicazione di rete, fino ad arrivare ad +un'implementazione completa. + +\subsection{Il client: prima versione} +\label{sec:TCP_echo_client} + +Il codice del client è riportato in \figref{fig:TCPsimpl_client_elem}, anche +esso ricalca la struttura del precedente client per il servizio +\texttt{daytime} (vedi \secref{sec:TCP_daytime_client}) ma, come per il +server, lo si è diviso in due parti, inserendo la parte relativa alle +operazioni specifiche previste per il protocollo \textit{echo} in una funzione +a parte. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6 cm} + \includecodesample{listati/EchoServerWrong.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione del client \textit{echo}.} + \label{fig:TCPsimpl_client_elem} +\end{figure} + +La funzione \code{main} si occupa della creazione del socket e della +connessione (linee \texttt{\small 10--27}) secondo la stessa modalità spiegata +in \secref{sec:TCP_daytime_client}, il client si connette sulla porta 7 +all'indirizzo specificato dalla linea di comando (a cui si è aggiunta una +elementare gestione delle opzioni non riportata in figura). + +Completata la connessione, al ritorno di \func{connect}, la funzione +\code{ClientEcho}, riportata in \figref{fig:TCPsimpl_client_echo_sub}, si +preoccupa di gestire la comunicazione, leggendo una riga alla volta dallo +\file{stdin}, scrivendola sul socket e ristampando su \file{stdout} quanto +ricevuto in risposta dal server. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ClientEcho.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione della funzione \texttt{ClientEcho} per + la gestione del servizio \textit{echo}.} + \label{fig:TCPsimpl_client_echo_sub} +\end{figure} + +La funzione utilizza due buffer per gestire i dati inviati e letti sul socket +(\texttt{\small 3}). La comunicazione viene gestita all'interno di un ciclo +(linee \texttt{\small 5--10}), i dati da inviare sulla connessione vengono +presi dallo \file{stdin} usando la funzione \func{fgets} che legge una +linea di testo (terminata da un \texttt{CR} e fino al massimo di +\const{MAXLINE} caratteri) e la salva sul buffer di invio, la funzione +\func{FullWrite} (\texttt{\small 3}) scrive detti dati sul socket (gestendo +l'invio multiplo qualora una singola \func{write} non basti, come spiegato +in \secref{sec:sock_io_behav}). + +I dati che vengono riletti indietro con una \func{FullRead} sul buffer di +ricezione e viene inserita la terminazione della stringa (\texttt{\small + 7--8}) e per poter usare la funzione \func{fputs} per scriverli su +\file{stdout}. + +Un end of file inviato su \file{stdin} causa il ritorno di \func{fgets} +con un puntatore nullo e l'uscita dal ciclo, al che la subroutine ritorna ed +il client esce. + + +\subsection{La struttura del server} +\label{sec:TCPsimp_server_main} + +Il servizio \textit{echo} è uno dei servizi standard solitamente provvisti +direttamente dal superserver \cmd{inetd}, ed è definito +dall'\href{http://www.ietf.org/rfc/rfc0862.txt}{RFC~862}. Come dice il nome il +servizio deve rimandare indietro sulla connessione i dati che gli vengono +inviati; l'RFC descrive le specifiche sia per TCP che UDP, e per il primo +stabilisce che una volta stabilita la connessione ogni dato in ingresso deve +essere rimandato in uscita, fintanto che il chiamante non ha chiude la +connessione; il servizio opera sulla porta 7. + +Nel nostro caso l'esempio sarà costituito da un client che legge una linea di +caratteri dallo standard input e la scrive sul server, il server leggerà la +linea dalla connessione e la riscriverà all'indietro; sarà compito del client +leggere la risposta del server e stamparla sullo standard output. + +La prima versione del server, \file{ElemEchoTCPServer.c}, si compone di un +corpo principale, costituito dalla funzione \code{main}. Questa si incarica +di creare il socket, metterlo in ascolto di connessioni in arrivo e creare un +processo figlio a cui delegare la gestione di ciascuna connessione. Questa +parte, riportata in \figref{fig:TCPsimpl_serv_code}, è analoga a quella vista +nel precedente esempio esaminato in \secref{sec:TCP_daytime_cunc_server}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ElemEchoTCPServer.c} + \end{minipage} + \normalsize + \caption{Codice della funzione \code{main} della prima versione del server + per il servizio \textit{echo}.} + \label{fig:TCPsimpl_serv_code} +\end{figure} + +La struttura di questa prima versione del server è sostanzialmente identica a +quella dell'esempio citato, ed ad esso si applicano le considerazioni fatte in +\secref{sec:TCP_daytime_cunc_server}. Le uniche differenze rispetto +all'esempio in \figref{fig:TCP_daytime_iter_server_code} sono che in questo +caso per il socket in ascolto viene usata la porta 7 e che tutta la gestione +della comunicazione è delegata alla funzione \code{ServEcho}. +% Per ogni connessione viene creato un +% processo figlio, il quale si incarica di lanciare la funzione +% \texttt{SockEcho}. + +Il codice della funzione \code{ServEcho} è invece mostrata in +\figref{fig:TCPsimpl_server_elem_sub}, la comunicazione viene gestita +all'interno del ciclo (linee \texttt{\small 6--8}). I dati inviati dal client +vengono letti dal socket con una semplice \func{read} (che ritorna solo in +presenza di dati in arrivo), la riscrittura viene invece gestita dalla +funzione \func{FullWrite} (descritta in \figref{fig:sock_FullWrite_code}) che +si incarica di tenere conto automaticamente della possibilità che non tutti i +dati di cui è richiesta la scrittura vengano trasmessi con una singola +\func{write}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ServEcho.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione della funzione \code{ServEcho} per la + gestione del servizio \textit{echo}.} + \label{fig:TCPsimpl_server_elem_sub} +\end{figure} + +Quando il client chiude la connessione il ricevimento del FIN fa ritornare la +\func{read} con un numero di byte letti pari a zero, il che causa l'uscita +dal ciclo e il ritorno della funzione, che a sua volta causa la terminazione +del processo figlio. + + + + +\subsection{L'avvio e il funzionamento normale} +\label{sec:TCPsimpl_startup} + +Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà +di considerare in dettaglio tutte le problematiche che si possono incontrare +nello scrivere un'applicazione di rete. Infatti attraverso l'esame delle sue +modalità di funzionamento normali, all'avvio e alla terminazione, e di quello +che avviene nelle varie situazioni limite, da una parte potremo approfondire +la comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni +necessarie per essere in grado di scrivere applicazioni robuste, in grado di +gestire anche i casi limite. + +Il primo passo è compilare e lanciare il server (da root, per poter usare la +porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con +la sequenza delle chiamate a \func{socket}, \func{bind}, \func{listen} e poi +si bloccherà nella \func{accept}. A questo punto si potrà controllarne lo +stato con \cmd{netstat}: +\begin{verbatim} +[piccardi@roke piccardi]$ netstat -at +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +... +tcp 0 0 *:echo *:* LISTEN +... +\end{verbatim} %$ +che ci mostra come il socket sia in ascolto sulla porta richiesta, accettando +connessioni da qualunque indirizzo e da qualunque porta e su qualunque +interfaccia locale. + +A questo punto si può lanciare il client, esso chiamerà \func{socket} e +\func{connect}; una volta completato il three way handshake la connessione è +stabilita; la \func{connect} ritornerà nel client\footnote{si noti che è + sempre la \func{connect} del client a ritornare per prima, in quanto + questo avviene alla ricezione del secondo segmento (l'ACK del server) del + three way handshake, la \func{accept} del server ritorna solo dopo + un altro mezzo RTT quando il terzo segmento (l'ACK del client) viene + ricevuto.} e la \func{accept} nel server, ed usando di nuovo +\cmd{netstat} otterremmo che: +\begin{verbatim} +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 *:echo *:* LISTEN +tcp 0 0 roke:echo gont:32981 ESTABLISHED +\end{verbatim} +mentre per quanto riguarda l'esecuzione dei programmi avremo che: +\begin{itemize} +\item il client chiama la funzione \code{ClientEcho} che si blocca sulla + \func{fgets} dato che non si è ancora scritto nulla sul terminale. +\item il server eseguirà una \func{fork} facendo chiamare al processo figlio + la funzione \code{ServEcho}, quest'ultima si bloccherà sulla \func{read} + dal socket sul quale ancora non sono presenti dati. +\item il processo padre del server chiamerà di nuovo \func{accept} + bloccandosi fino all'arrivo di un'altra connessione. +\end{itemize} +e se usiamo il comando \cmd{ps} per esaminare lo stato dei processi otterremo +un risultato del tipo: +\begin{verbatim} +[piccardi@roke piccardi]$ ps ax + PID TTY STAT TIME COMMAND + ... ... ... ... ... + 2356 pts/0 S 0:00 ./echod + 2358 pts/1 S 0:00 ./echo 127.0.0.1 + 2359 pts/0 S 0:00 ./echod +\end{verbatim} %$ +(dove si sono cancellate le righe inutili) da cui si evidenzia la presenza di +tre processi, tutti in stato di \textit{sleep} (vedi +\tabref{tab:proc_proc_states}). + +Se a questo punto si inizia a scrivere qualcosa sul client non sarà trasmesso +niente fin tanto che non si prema il tasto di a capo (si ricordi quanto detto +in \secref{sec:file_line_io} a proposito dell'I/O su terminale), solo allora +\func{fgets} ritornerà ed il client scriverà quanto immesso sul socket, per +poi passare a rileggere quanto gli viene inviato all'indietro dal server, che +a sua volta sarà inviato sullo standard output, che nel caso ne provoca +l'immediatamente stampa a video. + + +\subsection{La conclusione normale} +\label{sec:TCPsimpl_conclusion} + +Tutto quello che scriveremo sul client sarà rimandato indietro dal server e +ristampato a video fintanto che non concluderemo l'immissione dei dati; una +sessione tipica sarà allora del tipo: +\begin{verbatim} +[piccardi@roke sources]$ ./echo 127.0.0.1 +Questa e` una prova +Questa e` una prova +Ho finito +Ho finito +\end{verbatim} %$ +che termineremo inviando un EOF dal terminale (usando la combinazione di tasti +ctrl-D, che non compare a schermo); se eseguiamo un \cmd{netstat} a questo +punto avremo: +\begin{verbatim} +[piccardi@roke piccardi]$ netstat -at +tcp 0 0 *:echo *:* LISTEN +tcp 0 0 localhost:33032 localhost:echo TIME_WAIT +\end{verbatim} %$ +con il client che entra in \texttt{TIME\_WAIT}. + +Esaminiamo allora in dettaglio la sequenza di eventi che porta alla +terminazione normale della connessione, che ci servirà poi da riferimento +quando affronteremo il comportamento in caso di conclusioni anomale: + +\begin{enumerate} +\item inviando un carattere di EOF da terminale la \func{fgets} ritorna + restituendo un puntatore nullo che causa l'uscita dal ciclo di + \code{while}, così la \code{ClientEcho} ritorna. +\item al ritorno di \code{ClientEcho} ritorna anche la funzione \code{main}, e + come parte del processo terminazione tutti i file descriptor vengono chiusi + (si ricordi quanto detto in \secref{sec:proc_term_conclusion}); questo causa + la chiusura del socket di comunicazione; il client allora invierà un FIN al + server a cui questo risponderà con un ACK. A questo punto il client verrà a + trovarsi nello stato \texttt{FIN\_WAIT\_2} ed il server nello stato + \texttt{CLOSE\_WAIT} (si riveda quanto spiegato in + \secref{sec:TCP_conn_term}). +\item quando il server riceve il FIN la \func{read} del processo figlio che + gestisce la connessione ritorna restituendo 0 causando così l'uscita dal + ciclo e il ritorno di \code{ServEcho}, a questo punto il processo figlio + termina chiamando \func{exit}. +\item all'uscita del figlio tutti i file descriptor vengono chiusi, la + chiusura del socket connesso fa sì che venga effettuata la sequenza finale + di chiusura della connessione, viene emesso un FIN dal server che riceverà + un ACK dal client, a questo punto la connessione è conclusa e il client + resta nello stato \texttt{TIME\_WAIT}. + +\end{enumerate} + + +\subsection{La gestione dei processi figli} +\label{sec:TCPsimpl_child_hand} + +Tutto questo riguarda la connessione, c'è però da tenere conto dell'effetto +del procedimento di chiusura del processo figlio nel server (si veda quanto +esaminato in \secref{sec:proc_termination}). In questo caso avremo l'invio del +segnale \const{SIGCHLD} al padre, ma dato che non si è installato un +gestore e che l'azione predefinita per questo segnale è quella di essere +ignorato, non avendo predisposto la ricezione dello stato di terminazione, +otterremo che il processo figlio entrerà nello stato di zombie\index{zombie} +(si riveda quanto illustrato in \secref{sec:sig_sigchld}), come risulterà +ripetendo il comando \cmd{ps}: +\begin{verbatim} + 2356 pts/0 S 0:00 ./echod + 2359 pts/0 Z 0:00 [echod ] +\end{verbatim} + +Poiché non è possibile lasciare processi zombie\index{zombie} che pur inattivi +occupano spazio nella tabella dei processi e a lungo andare saturerebbero le +risorse del kernel, occorrerà ricevere opportunamente lo stato di terminazione +del processo (si veda \secref{sec:proc_wait}), cosa che faremo utilizzando +\const{SIGCHLD} secondo quanto illustrato in \secref{sec:sig_sigchld}. + +La prima modifica al nostro server è pertanto quella di inserire la gestione +della terminazione dei processi figli attraverso l'uso di un gestore. +Per questo useremo la funzione \code{Signal}, illustrata in +\figref{fig:sig_Signal_code}, per installare il semplice gestore che +riceve i segnali dei processi figli terminati già visto in +\figref{fig:sig_sigchld_handl}; aggiungendo il seguente codice: +\includecodesnip{listati/sigchildhand.c} +\noindent +all'esempio illustrato in \figref{fig:TCPsimpl_serv_code}, e linkando il tutto +alla funzione \code{sigchld\_hand}, si risolverà completamente il problema +degli zombie\index{zombie}. + + + %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" diff --git a/gapil.tex b/gapil.tex index dae2d68..4503ec9 100644 --- a/gapil.tex +++ b/gapil.tex @@ -141,7 +141,7 @@ \include{network} \include{socket} \include{elemtcp} -\include{simpltcp} +%\include{simpltcp} \appendix \include{netlayer} \include{trasplayer} diff --git a/listati/ElemDaytimeTCPClient.c b/listati/ElemDaytimeTCPClient.c deleted file mode 100644 index e1459d9..0000000 --- a/listati/ElemDaytimeTCPClient.c +++ /dev/null @@ -1,48 +0,0 @@ -#include /* predefined types */ -#include /* include unix standard library */ -#include /* IP addresses conversion utilities */ -#include /* socket library */ -#include /* include standard I/O library */ - -int main(int argc, char *argv[]) -{ - int sock_fd; - int i, nread; - struct sockaddr_in serv_add; - char buffer[MAXLINE]; - ... - /* create socket */ - if ( (sock_fd = 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(13); /* daytime post is 13 */ - /* 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_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) { - perror("Connection error"); - return -1; - } - /* read daytime from server */ - while ( (nread = read(sock_fd, buffer, MAXLINE)) > 0) { - buffer[nread]=0; - if (fputs(buffer, stdout) == EOF) { /* write daytime */ - perror("fputs error"); - return -1; - } - } - /* error on read */ - if (nread < 0) { - perror("Read error"); - return -1; - } - /* normal exit */ - return 0; -} diff --git a/listati/TCP_daytime.c b/listati/TCP_daytime.c new file mode 100644 index 0000000..e1459d9 --- /dev/null +++ b/listati/TCP_daytime.c @@ -0,0 +1,48 @@ +#include /* predefined types */ +#include /* include unix standard library */ +#include /* IP addresses conversion utilities */ +#include /* socket library */ +#include /* include standard I/O library */ + +int main(int argc, char *argv[]) +{ + int sock_fd; + int i, nread; + struct sockaddr_in serv_add; + char buffer[MAXLINE]; + ... + /* create socket */ + if ( (sock_fd = 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(13); /* daytime post is 13 */ + /* 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_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) { + perror("Connection error"); + return -1; + } + /* read daytime from server */ + while ( (nread = read(sock_fd, buffer, MAXLINE)) > 0) { + buffer[nread]=0; + if (fputs(buffer, stdout) == EOF) { /* write daytime */ + perror("fputs error"); + return -1; + } + } + /* error on read */ + if (nread < 0) { + perror("Read error"); + return -1; + } + /* normal exit */ + return 0; +} diff --git a/simpltcp.tex b/simpltcp.tex deleted file mode 100644 index 7d0291f..0000000 --- a/simpltcp.tex +++ /dev/null @@ -1,346 +0,0 @@ -%% simpltcp.tex -%% -%% Copyright (C) 2000-2002 Simone Piccardi. Permission is granted to -%% copy, distribute and/or modify this document under the terms of the GNU Free -%% Documentation License, Version 1.1 or any later version published by the -%% Free Software Foundation; with the Invariant Sections being "Prefazione", -%% with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the -%% license is included in the section entitled "GNU Free Documentation -%% License". -%% -\chapter{Un esempio completo di client/server TCP} -\label{cha:simple_TCP_sock} - -In questo capitolo riprenderemo le funzioni trattate nel precedente, usandole -per scrivere una prima applicazione client/server che usi i socket TCP per una -comunicazione in entrambe le direzioni. - -Inoltre prenderemo in esame, oltre al comportamento in condizioni normali, -anche tutti i possibili scenari particolari (errori, sconnessione della rete, -crash del client o del server durante la connessione) che possono avere luogo -durante l'impiego di un'applicazione di rete. - - -\section{Il servizio \texttt{echo}} -\label{sec:TCPsimp_echo} - -L'applicazione scelta come esempio sarà un'implementazione elementare, ma -completa, del servizio \texttt{echo}. Il servizio \texttt{echo} è uno dei -servizi standard solitamente provvisti direttamente dal superserver -\cmd{inetd}, ed è definito dall'RFC~862. Come dice il nome il servizio deve -rimandare indietro sulla connessione i dati che gli vengono inviati; l'RFC -descrive le specifiche sia per TCP che UDP, e per il primo stabilisce che una -volta stabilita la connessione ogni dato in ingresso deve essere rimandato in -uscita, fintanto che il chiamante non ha chiude la connessione; il servizio -opera sulla porta 7. - -Nel nostro caso l'esempio sarà costituito da un client che legge una linea di -caratteri dallo standard input e la scrive sul server, il server leggerà la -linea dalla connessione e la riscriverà all'indietro; sarà compito del client -leggere la risposta del server e stamparla sullo standard output. - -Si è scelto di usare questo servizio, seguendo l'esempio di \cite{UNP1}, -perché costituisce il prototipo ideale di una generica applicazione di rete in -cui un server risponde alle richieste di un client; tutto quello che cambia -nel caso si una applicazione più complessa è la elaborazione dell'input del -client da parte del server nel fornire le risposte in uscita. - -Partiremo da un'implementazione elementare che dovrà essere rimaneggiata di -volta in volta per poter tenere conto di tutte le evenienze che si possono -manifestare nella vita reale di un'applicazione di rete, fino ad arrivare ad -un'implementazione completa. - -\subsection{La struttura del server} -\label{sec:TCPsimp_server_main} - -La prima versione del server, \file{ElemEchoTCPServer.c}, si compone di un -corpo principale, costituito dalla funzione \code{main}. Questa si incarica -di creare il socket, metterlo in ascolto di connessioni in arrivo e creare un -processo figlio a cui delegare la gestione di ciascuna connessione. Questa -parte, riportata in \figref{fig:TCPsimpl_serv_code}, è analoga a quella vista -nel precedente esempio esaminato in \secref{sec:TCP_cunc_daytime}. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/ElemEchoTCPServer.c} - \end{minipage} - \normalsize - \caption{Codice della funzione \code{main} della prima versione del server - per il servizio \texttt{echo}.} - \label{fig:TCPsimpl_serv_code} -\end{figure} - -La struttura di questa prima versione del server è sostanzialmente identica a -quella dell'esempio citato, ed ad esso si applicano le considerazioni fatte in -\secref{sec:TCP_cunc_daytime}. Le uniche differenze rispetto all'esempio in -\figref{fig:TCP_serv_code} sono che in questo caso per il socket in ascolto -viene usata la porta 7 e che tutta la gestione della comunicazione è delegata -alla funzione \code{ServEcho}. -% Per ogni connessione viene creato un -% processo figlio, il quale si incarica di lanciare la funzione -% \texttt{SockEcho}. - -Il codice della funzione \code{ServEcho} è invece mostrata in -\figref{fig:TCPsimpl_server_elem_sub}, la comunicazione viene gestita -all'interno del ciclo (linee \texttt{\small 6--8}). I dati inviati dal client -vengono letti dal socket con una semplice \func{read} (che ritorna solo in -presenza di dati in arrivo), la riscrittura viene invece gestita dalla -funzione \func{FullWrite} (descritta in \figref{fig:sock_FullWrite_code}) che -si incarica di tenere conto automaticamente della possibilità che non tutti i -dati di cui è richiesta la scrittura vengano trasmessi con una singola -\func{write}. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/ServEcho.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione della funzione \code{ServEcho} per la - gestione del servizio \texttt{echo}.} - \label{fig:TCPsimpl_server_elem_sub} -\end{figure} - -Quando il client chiude la connessione il ricevimento del FIN fa ritornare la -\func{read} con un numero di byte letti pari a zero, il che causa l'uscita -dal ciclo e il ritorno della funzione, che a sua volta causa la terminazione -del processo figlio. - - -\subsection{Il client} -\label{sec:TCPsimp_client_main} - -Il codice del client è riportato in \figref{fig:TCPsimpl_client_elem}, anche -esso ricalca la struttura del precedente client per il servizio -\texttt{daytime} (vedi \secref{sec:TCP_cli_sample}) ma, come per il server, lo -si è diviso in due parti, inserendo la parte relativa alle operazioni -specifiche previste per il protocollo \texttt{echo} in una funzione a parte. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6 cm} - \includecodesample{listati/EchoServerWrong.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione del client \texttt{echo}.} - \label{fig:TCPsimpl_client_elem} -\end{figure} - -La funzione \code{main} si occupa della creazione del socket e della -connessione (linee \texttt{\small 10--27}) secondo la stessa modalità spiegata -in \secref{sec:TCP_cli_sample}, il client si connette sulla porta 7 -all'indirizzo specificato dalla linea di comando (a cui si è aggiunta una -elementare gestione delle opzioni non riportata in figura). - -Completata la connessione, al ritorno di \func{connect}, la funzione -\code{ClientEcho}, riportata in \figref{fig:TCPsimpl_client_echo_sub}, si -preoccupa di gestire la comunicazione, leggendo una riga alla volta dallo -\file{stdin}, scrivendola sul socket e ristampando su \file{stdout} quanto -ricevuto in risposta dal server. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/ClientEcho.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione della funzione \texttt{ClientEcho} per - la gestione del servizio \texttt{echo}.} - \label{fig:TCPsimpl_client_echo_sub} -\end{figure} - -La funzione utilizza due buffer per gestire i dati inviati e letti sul socket -(\texttt{\small 3}). La comunicazione viene gestita all'interno di un ciclo -(linee \texttt{\small 5--10}), i dati da inviare sulla connessione vengono -presi dallo \file{stdin} usando la funzione \func{fgets} che legge una -linea di testo (terminata da un \texttt{CR} e fino al massimo di -\const{MAXLINE} caratteri) e la salva sul buffer di invio, la funzione -\func{FullWrite} (\texttt{\small 3}) scrive detti dati sul socket (gestendo -l'invio multiplo qualora una singola \func{write} non basti, come spiegato -in \secref{sec:sock_io_behav}). - -I dati che vengono riletti indietro con una \func{FullRead} sul buffer di -ricezione e viene inserita la terminazione della stringa (\texttt{\small - 7--8}) e per poter usare la funzione \func{fputs} per scriverli su -\file{stdout}. - -Un end of file inviato su \file{stdin} causa il ritorno di \func{fgets} -con un puntatore nullo e l'uscita dal ciclo, al che la subroutine ritorna ed -il client esce. - - -\section{Il funzionamento del servizio} -\label{sec:TCPsimpl_normal_work} - -Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà -di considerare in dettaglio tutte le problematiche che si possono incontrare -nello scrivere un'applicazione di rete. Infatti attraverso l'esame delle sue -modalità di funzionamento normali, all'avvio e alla terminazione, e di quello -che avviene nelle varie situazioni limite, da una parte potremo approfondire -la comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni -necessarie per essere in grado di scrivere applicazioni robuste, in grado di -gestire anche i casi limite. - - -\subsection{L'avvio e il funzionamento} -\label{sec:TCPsimpl_startup} - -Il primo passo è compilare e lanciare il server (da root, per poter usare la -porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con -la sequenza delle chiamate a \func{socket}, \func{bind}, \func{listen} e poi -si bloccherà nella \func{accept}. A questo punto si potrà controllarne lo -stato con \cmd{netstat}: -\begin{verbatim} -[piccardi@roke piccardi]$ netstat -at -Active Internet connections (servers and established) -Proto Recv-Q Send-Q Local Address Foreign Address State -... -tcp 0 0 *:echo *:* LISTEN -... -\end{verbatim} %$ -che ci mostra come il socket sia in ascolto sulla porta richiesta, accettando -connessioni da qualunque indirizzo e da qualunque porta e su qualunque -interfaccia locale. - -A questo punto si può lanciare il client, esso chiamerà \func{socket} e -\func{connect}; una volta completato il three way handshake la connessione è -stabilita; la \func{connect} ritornerà nel client\footnote{si noti che è - sempre la \func{connect} del client a ritornare per prima, in quanto - questo avviene alla ricezione del secondo segmento (l'ACK del server) del - three way handshake, la \func{accept} del server ritorna solo dopo - un altro mezzo RTT quando il terzo segmento (l'ACK del client) viene - ricevuto.} e la \func{accept} nel server, ed usando di nuovo -\cmd{netstat} otterremmo che: -\begin{verbatim} -Active Internet connections (servers and established) -Proto Recv-Q Send-Q Local Address Foreign Address State -tcp 0 0 *:echo *:* LISTEN -tcp 0 0 roke:echo gont:32981 ESTABLISHED -\end{verbatim} -mentre per quanto riguarda l'esecuzione dei programmi avremo che: -\begin{itemize} -\item il client chiama la funzione \code{ClientEcho} che si blocca sulla - \func{fgets} dato che non si è ancora scritto nulla sul terminale. -\item il server eseguirà una \func{fork} facendo chiamare al processo figlio - la funzione \code{ServEcho}, quest'ultima si bloccherà sulla \func{read} - dal socket sul quale ancora non sono presenti dati. -\item il processo padre del server chiamerà di nuovo \func{accept} - bloccandosi fino all'arrivo di un'altra connessione. -\end{itemize} -e se usiamo il comando \cmd{ps} per esaminare lo stato dei processi otterremo -un risultato del tipo: -\begin{verbatim} -[piccardi@roke piccardi]$ ps ax - PID TTY STAT TIME COMMAND - ... ... ... ... ... - 2356 pts/0 S 0:00 ./echod - 2358 pts/1 S 0:00 ./echo 127.0.0.1 - 2359 pts/0 S 0:00 ./echod -\end{verbatim} %$ -(dove si sono cancellate le righe inutili) da cui si evidenzia la presenza di -tre processi, tutti in stato di \textit{sleep} (vedi -\tabref{tab:proc_proc_states}). - -Se a questo punto si inizia a scrivere qualcosa sul client non sarà trasmesso -niente fin tanto che non si prema il tasto di a capo (si ricordi quanto detto -in \secref{sec:file_line_io} a proposito dell'I/O su terminale), solo allora -\func{fgets} ritornerà ed il client scriverà quanto immesso sul socket, per -poi passare a rileggere quanto gli viene inviato all'indietro dal server, che -a sua volta sarà inviato sullo standard output, che nel caso ne provoca -l'immediatamente stampa a video. - - -\subsection{La conclusione normale} -\label{sec:TCPsimpl_conclusion} - -Tutto quello che scriveremo sul client sarà rimandato indietro dal server e -ristampato a video fintanto che non concluderemo l'immissione dei dati; una -sessione tipica sarà allora del tipo: -\begin{verbatim} -[piccardi@roke sources]$ ./echo 127.0.0.1 -Questa e` una prova -Questa e` una prova -Ho finito -Ho finito -\end{verbatim} %$ -che termineremo inviando un EOF dal terminale (usando la combinazione di tasti -ctrl-D, che non compare a schermo); se eseguiamo un \cmd{netstat} a questo -punto avremo: -\begin{verbatim} -[piccardi@roke piccardi]$ netstat -at -tcp 0 0 *:echo *:* LISTEN -tcp 0 0 localhost:33032 localhost:echo TIME_WAIT -\end{verbatim} %$ -con il client che entra in \texttt{TIME\_WAIT}. - -Esaminiamo allora in dettaglio la sequenza di eventi che porta alla -terminazione normale della connessione, che ci servirà poi da riferimento -quando affronteremo il comportamento in caso di conclusioni anomale: - -\begin{enumerate} -\item inviando un carattere di EOF da terminale la \func{fgets} ritorna - restituendo un puntatore nullo che causa l'uscita dal ciclo di - \code{while}, così la \code{ClientEcho} ritorna. -\item al ritorno di \code{ClientEcho} ritorna anche la funzione \code{main}, e - come parte del processo terminazione tutti i file descriptor vengono chiusi - (si ricordi quanto detto in \secref{sec:proc_term_conclusion}); questo causa - la chiusura del socket di comunicazione; il client allora invierà un FIN al - server a cui questo risponderà con un ACK. A questo punto il client verrà a - trovarsi nello stato \texttt{FIN\_WAIT\_2} ed il server nello stato - \texttt{CLOSE\_WAIT} (si riveda quanto spiegato in - \secref{sec:TCP_conn_term}). -\item quando il server riceve il FIN la \func{read} del processo figlio che - gestisce la connessione ritorna restituendo 0 causando così l'uscita dal - ciclo e il ritorno di \code{ServEcho}, a questo punto il processo figlio - termina chiamando \func{exit}. -\item all'uscita del figlio tutti i file descriptor vengono chiusi, la - chiusura del socket connesso fa sì che venga effettuata la sequenza finale - di chiusura della connessione, viene emesso un FIN dal server che riceverà - un ACK dal client, a questo punto la connessione è conclusa e il client - resta nello stato \texttt{TIME\_WAIT}. - -\end{enumerate} - - -\subsection{La gestione dei processi figli} -\label{sec:TCPsimpl_child_hand} - -Tutto questo riguarda la connessione, c'è però da tenere conto dell'effetto -del procedimento di chiusura del processo figlio nel server (si veda quanto -esaminato in \secref{sec:proc_termination}). In questo caso avremo l'invio del -segnale \const{SIGCHLD} al padre, ma dato che non si è installato un -gestore e che l'azione predefinita per questo segnale è quella di essere -ignorato, non avendo predisposto la ricezione dello stato di terminazione, -otterremo che il processo figlio entrerà nello stato di zombie\index{zombie} -(si riveda quanto illustrato in \secref{sec:sig_sigchld}), come risulterà -ripetendo il comando \cmd{ps}: -\begin{verbatim} - 2356 pts/0 S 0:00 ./echod - 2359 pts/0 Z 0:00 [echod ] -\end{verbatim} - -Poiché non è possibile lasciare processi zombie\index{zombie} che pur inattivi -occupano spazio nella tabella dei processi e a lungo andare saturerebbero le -risorse del kernel, occorrerà ricevere opportunamente lo stato di terminazione -del processo (si veda \secref{sec:proc_wait}), cosa che faremo utilizzando -\const{SIGCHLD} secondo quanto illustrato in \secref{sec:sig_sigchld}. - -La prima modifica al nostro server è pertanto quella di inserire la gestione -della terminazione dei processi figli attraverso l'uso di un gestore. -Per questo useremo la funzione \code{Signal}, illustrata in -\figref{fig:sig_Signal_code}, per installare il semplice gestore che -riceve i segnali dei processi figli terminati già visto in -\figref{fig:sig_sigchld_handl}; aggiungendo il seguente codice: -\includecodesnip{listati/sigchildhand.c} -\noindent -all'esempio illustrato in \figref{fig:TCPsimpl_serv_code}, e linkando il tutto -alla funzione \code{sigchld\_hand}, si risolverà completamente il problema -degli zombie\index{zombie}. - - - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "gapil" -%%% End: diff --git a/sources/ElemDaytimeTCPClient.c b/sources/ElemDaytimeTCPClient.c deleted file mode 100644 index d049b7d..0000000 --- a/sources/ElemDaytimeTCPClient.c +++ /dev/null @@ -1,127 +0,0 @@ -/* ElemDaytimeTCPClient.c - * - * Copyright (C) 2001 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 daytime: - * Elementary TCP client for daytime service (port 13) - * - * Author: Simone Piccardi - * Apr. 2001 - * - * Usage: daytime -h give all info's - * - * $Id: ElemDaytimeTCPClient.c,v 1.2 2001/09/09 22:45:34 piccardi Exp $ - * - ****************************************************************/ -/* - * Include needed headers - */ -#include /* predefined types */ -#include /* include unix standard library */ -#include /* IP addresses conversion utiliites */ -#include /* socket library */ -#include /* include standard I/O library */ - -#define MAXLINE 80 -/* Program begin */ -void usage(void); -int main(int argc, char *argv[]) -{ -/* - * Variables definition - */ - int sock_fd; - int i, nread; - struct sockaddr_in serv_add; - char buffer[MAXLINE]; - /* - * Input section: decode parameters passed in the calling - * Use getopt function - */ - opterr = 0; /* don't want writing to stderr */ - while ( (i = getopt(argc, argv, "h")) != -1) { - switch (i) { - /* - * Handling options - */ - case 'h': - printf("Wrong -h option use\n"); - usage(); - return(0); - 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_fd = 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(13); /* daytime port is 13 */ - /* 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_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) { - perror("Connection error"); - return -1; - } - /* read daytime from server */ - while ( (nread = read(sock_fd, buffer, MAXLINE)) > 0) { - buffer[nread]=0; - if (fputs(buffer, stdout) == EOF) { /* write daytime */ - perror("fputs error"); - return -1; - } - } - /* error on read */ - if (nread < 0) { - perror("Read error"); - return -1; - } - /* 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(" -h print this help\n"); - exit(1); -} diff --git a/sources/Makefile b/sources/Makefile index 59772a8..31fff96 100644 --- a/sources/Makefile +++ b/sources/Makefile @@ -78,7 +78,7 @@ daytimed: ElemDaytimeTCPCuncServ.c iterdaytimed: ElemDaytimeTCPServer.c $(CC) $(CFLAGJ) $^ -o $@ -daytime: ElemDaytimeTCPClient.c +daytime: TCP_daytime.c $(CC) $(CFLAGJ) $^ -o $@ ipctestid: IPCTestId.c diff --git a/sources/TCP_daytime.c b/sources/TCP_daytime.c new file mode 100644 index 0000000..18ce8d4 --- /dev/null +++ b/sources/TCP_daytime.c @@ -0,0 +1,127 @@ +/* ElemDaytimeTCPClient.c + * + * Copyright (C) 2001 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 daytime: + * Elementary TCP client for daytime service (port 13) + * + * Author: Simone Piccardi + * Apr. 2001 + * + * Usage: daytime -h give all info's + * + * $Id: TCP_daytime.c,v 1.1 2003/04/28 14:13:50 piccardi Exp $ + * + ****************************************************************/ +/* + * Include needed headers + */ +#include /* predefined types */ +#include /* include unix standard library */ +#include /* IP addresses conversion utiliites */ +#include /* socket library */ +#include /* include standard I/O library */ + +#define MAXLINE 80 +/* Program begin */ +void usage(void); +int main(int argc, char *argv[]) +{ +/* + * Variables definition + */ + int sock_fd; + int i, nread; + struct sockaddr_in serv_add; + char buffer[MAXLINE]; + /* + * Input section: decode parameters passed in the calling + * Use getopt function + */ + opterr = 0; /* don't want writing to stderr */ + while ( (i = getopt(argc, argv, "h")) != -1) { + switch (i) { + /* + * Handling options + */ + case 'h': + printf("Wrong -h option use\n"); + usage(); + return(0); + 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_fd = 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(13); /* daytime port is 13 */ + /* 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_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) { + perror("Connection error"); + return -1; + } + /* read daytime from server */ + while ( (nread = read(sock_fd, buffer, MAXLINE)) > 0) { + buffer[nread]=0; + if (fputs(buffer, stdout) == EOF) { /* write daytime */ + perror("fputs error"); + return -1; + } + } + /* error on read */ + if (nread < 0) { + perror("Read error"); + return -1; + } + /* 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(" -h print this help\n"); + exit(1); +}