1 \chapter{Un esempio completo di client/server TCP}
2 \label{cha:simple_TCP_sock}
4 In questo capitolo riprenderemo le funzioni trattate nel precedente, usandole
5 per scrivere una prima applicazione client/server che usi i socket TCP per una
6 comunicazione in entrambe le direzioni.
8 Inoltre prenderemo in esame, oltre al comportamento in condizioni normali,
9 anche tutti i possibili scenari particolari (errori, sconnessione della rete,
10 crash del client o del server durante la connessione) che possono avere luogo
11 durante l'impiego di un'applicazione di rete.
14 \section{Il servizio \texttt{echo}}
15 \label{sec:TCPsimp_echo}
17 L'applicazione scelta come esempio sarà un'implementazione elementare, ma
18 completa, del servizio \texttt{echo}. Il servizio \texttt{echo} è uno dei
19 servizi standard solitamente provvisti direttamente dal superserver
20 \cmd{inetd}, ed è definito dall'RFC~862. Come dice il nome il servizio deve
21 rimandare indietro sulla connessione i dati che gli vengono inviati; l'RFC
22 descrive le specifiche sia per TCP che UDP, e per il primo stabilisce che una
23 volta stabilita la connessione ogni dato in ingresso deve essere rimandato in
24 uscita, fintanto che il chiamante non ha chiude la connessione; il servizio
27 Nel nostro caso l'esempio sarà costituito da un client che legge una linea di
28 caratteri dallo standard input e la scrive sul server, il server leggerà la
29 linea dalla connessione e la riscriverà all'indietro; sarà compito del client
30 leggere la risposta del server e stamparla sullo standard output.
32 Si è scelto di usare questo servizio, seguendo l'esempio di \cite{UNP1},
33 perché costituisce il prototipo ideale di una generica applicazione di rete in
34 cui un server risponde alle richieste di un client; tutto quello che cambia
35 nel caso si una applicazione più complessa è la elaborazione dell'input del
36 client da parte del server nel fornire le risposte in uscita.
38 Partiremo da un'implementazione elementare che dovrà essere rimaneggiata di
39 volta in volta per poter tenere conto di tutte le evenienze che si possono
40 manifestare nella vita reale di un'applicazione di rete, fino ad arrivare ad
41 un'implementazione completa.
43 \subsection{La struttura del server}
44 \label{sec:TCPsimp_server_main}
46 La prima versione del server, \file{ElemEchoTCPServer.c}, si compone di un
47 corpo principale, costituito dalla funzione \code{main}. Questa si incarica
48 di creare il socket, metterlo in ascolto di connessioni in arrivo e creare un
49 processo figlio a cui delegare la gestione di ciascuna connessione. Questa
50 parte, riportata in \figref{fig:TCPsimpl_serv_code}, è analoga a quella vista
51 nel precedente esempio esaminato in \secref{sec:TCPel_cunc_serv}.
56 /* Subroutines declaration */
57 void ServEcho(int sockfd);
58 /* Program beginning */
59 int main(int argc, char *argv[])
63 struct sockaddr_in serv_add;
66 if ( (list_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
67 perror("Socket creation error");
70 /* initialize address */
71 memset((void *)&serv_add, 0, sizeof(serv_add)); /* clear server address */
72 serv_add.sin_family = AF_INET; /* address type is INET */
73 serv_add.sin_port = htons(13); /* daytime port is 13 */
74 serv_add.sin_addr.s_addr = htonl(INADDR_ANY); /* connect from anywhere */
76 if (bind(list_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
80 /* listen on socket */
81 if (listen(list_fd, BACKLOG) < 0 ) {
82 perror("listen error");
85 /* handle echo to client */
87 /* accept connection */
88 if ( (conn_fd = accept(list_fd, NULL, NULL)) < 0) {
89 perror("accept error");
92 /* fork to handle connection */
93 if ( (pid = fork()) < 0 ){
97 if (pid == 0) { /* child */
98 close(list_fd); /* close listening socket */
99 SockEcho(conn_fd); /* handle echo */
101 } else { /* parent */
102 close(conn_fd); /* close connected socket */
105 /* normal exit, never reached */
109 \caption{Codice della funzione \code{main} della prima versione del server
110 per il servizio \texttt{echo}.}
111 \label{fig:TCPsimpl_serv_code}
114 La struttura di questa prima versione del server è sostanzialmente identica a
115 quella dell'esempio citato, ed ad esso si applicano le considerazioni fatte in
116 \secref{sec:TCPel_cunc_daytime}. Le uniche differenze rispetto all'esempio in
117 \figref{fig:TCPel_serv_code} sono che in questo caso per il socket in ascolto
118 viene usata la porta 7 e che tutta la gestione della comunicazione è delegata
119 alla funzione \code{ServEcho}.
120 % Per ogni connessione viene creato un
121 % processo figlio, il quale si incarica di lanciare la funzione
124 Il codice della funzione \code{ServEcho} è invece mostrata in \nfig, la
125 comunicazione viene gestita all'interno del ciclo (linee \texttt{\small
126 6--8}). I dati inviati dal client vengono letti dal socket con una semplice
127 \func{read} (che ritorna solo in presenza di dati in arrivo), la riscrittura
128 viene invece gestita dalla funzione \func{SockWrite} (descritta in
129 \figref{fig:sock_SockWrite_code}) che si incarica di tenere conto
130 automaticamente della possibilità che non tutti i dati di cui è richiesta la
131 scrittura vengano trasmessi con una singola \func{write}.
136 void ServEcho(int sockfd) {
137 char buffer[MAXLINE];
140 /* main loop, reading 0 char means client close connection */
141 while ( (nread = read(sockfd, buffer, MAXLINE)) != 0) {
142 nwrite = SockWrite(sockfd, buffer, nread);
147 \caption{Codice della prima versione della funzione \code{ServEcho} per la
148 gestione del servizio \texttt{echo}.}
149 \label{fig:TCPsimpl_server_elem_sub}
152 Quando il client chiude la connessione il ricevimento del FIN fa ritornare la
153 \func{read} con un numero di byte letti pari a zero, il che causa l'uscita
154 dal ciclo e il ritorno della funzione, che a sua volta causa la terminazione
158 \subsection{Il client}
159 \label{sec:TCPsimp_client_main}
161 Il codice del client è riportato in \figref{fig:TCPsimpl_client_elem}, anche
162 esso ricalca la struttura del precedente client per il servizio
163 \texttt{daytime} (vedi \secref{sec:net_cli_sample}) ma, come per il server, lo
164 si è diviso in due parti, inserendo la parte relativa alle operazioni
165 specifiche previste per il protocollo \texttt{echo} in una funzione a parte.
169 int main(int argc, char *argv[])
172 * Variables definition
175 struct sockaddr_in serv_add;
178 if ( (sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
179 perror("Socket creation error");
182 /* initialize address */
183 memset((void *) &serv_add, 0, sizeof(serv_add)); /* clear server address */
184 serv_add.sin_family = AF_INET; /* address type is INET */
185 serv_add.sin_port = htons(7); /* echo port is 7 */
186 /* build address using inet_pton */
187 if ( (inet_pton(AF_INET, argv[optind], &serv_add.sin_addr)) <= 0) {
188 perror("Address creation error");
191 /* extablish connection */
192 if (connect(sock_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
193 perror("Connection error");
196 /* read daytime from server */
197 ClientEcho(stdin, sock_fd);
202 \caption{Codice della prima versione del client \texttt{echo}.}
203 \label{fig:TCPsimpl_client_elem}
206 La funzione \code{main} si occupa della creazione del socket e della
207 connessione (linee \texttt{\small 10--27}) secondo la stessa modalità spiegata
208 in \secref{sec:net_cli_sample}, il client si connette sulla porta 7
209 all'indirizzo specificato dalla linea di comando (a cui si è aggiunta una
210 elementare gestione delle opzioni non riportata in figura).
212 Completata la connessione, al ritrno fiììdi \func{connect} è ritornata, la
213 funzione \code{ClientEcho}, riportata in
214 \figref{fig:TCPsimpl_client_echo_sub}, si preoccupa di gestire la
215 comunicazione, leggendo una riga alla volta dallo \file{stdin}, scrivendola
216 sul socket e ristampando su \file{stdout} quanto ricevuto in risposta dal
222 void ClientEcho(FILE * filein, int socket)
224 char sendbuff[MAXLINE], recvbuff[MAXLINE];
226 while (fgets(sendbuff, MAXLINE, filein) != NULL) {
227 SockWrite(socket, sendbuff, strlen(sendbuff));
228 nread = SockRead(socket, recvbuff, strlen(sendbuff));
230 fputs(recvbuff, stdout);
235 \caption{Codice della prima versione della funzione \texttt{ClientEcho} per
236 la gestione del servizio \texttt{echo}.}
237 \label{fig:TCPsimpl_client_echo_sub}
240 La funzione utilizza due buffer per gestire i dati inviati e letti sul socket
241 (\texttt{\small 3}). La comunicazione viene gestita all'interno di un ciclo
242 (linee \texttt{\small 5--10}), i dati da inviare sulla connessione vengono
243 presi dallo \file{stdin} usando la funzione \func{fgets} che legge una
244 linea di testo (terminata da un \texttt{CR} e fino al massimo di
245 \macro{MAXLINE} caratteri) e la salva sul buffer di invio, la funzione
246 \func{SockWrite} (\texttt{\small 3}) scrive detti dati sul socket (gestendo
247 l'invio multiplo qualora una singola \func{write} non basti, come spiegato
248 in \secref{sec:sock_io_behav}).
250 I dati che vengono riletti indietro con una \func{SockRead} sul buffer di
251 ricezione e viene inserita la terminazione della stringa (\texttt{\small
252 7--8}) e per poter usare la funzione \func{fputs} per scriverli su
255 Un end of file inviato su \file{stdin} causa il ritorno di \func{fgets}
256 con un puntatore nullo e l'uscita dal ciclo, al che la subroutine ritorna ed
260 \section{Il funzionamento del servizio}
261 \label{sec:TCPsimpl_normal_work}
263 Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà
264 di considerare in dettaglio tutte le problematiche che si possono incontrare
265 nello scrivere un'applicazione di rete. Infatti attraverso l'esame delle sue
266 modalità di funzionamento normali, all'avvio e alla terminazione, e di quello
267 che avviene nelle varie situazioni limite, da una parte potremo approfondire
268 la comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni
269 necessarie per essere in grado di scrivere applicazioni robuste, in grado di
270 gestire anche i casi limite.
273 \subsection{L'avvio e il funzionamento}
274 \label{sec:TCPsimpl_startup}
276 Il primo passo è compilare e lanciare il server (da root, per poter usare la
277 porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con
278 la sequenza delle chiamate a \func{socket}, \func{bind}, \func{listen} e poi
279 si bloccherà nella \func{accept}. A questo punto si potrà controllarne lo
280 stato con \cmd{netstat}:
282 [piccardi@roke piccardi]$ netstat -at
283 Active Internet connections (servers and established)
284 Proto Recv-Q Send-Q Local Address Foreign Address State
286 tcp 0 0 *:echo *:* LISTEN
289 che ci mostra come il socket sia in ascolto sulla porta richiesta, accettando
290 connessioni da qualunque indirizzo e da qualunque porta e su qualunque
293 A questo punto si può lanciare il client, esso chiamerà \func{socket} e
294 \func{connect}; una volta completato il three way handshake la connessione è
295 stabilita; la \func{connect} ritornerà nel client\footnote{si noti che è
296 sempre la \func{connect} del client a ritornare per prima, in quanto
297 questo avviene alla ricezione del secondo segmento (l'ACK del server) del
298 three way handshake, la \func{accept} del server ritorna solo dopo
299 un altro mezzo RTT quando il terzo segmento (l'ACK del client) viene
300 ricevuto.} e la \func{accept} nel server, ed usando di nuovo
301 \cmd{netstat} otterremmo che:
303 Active Internet connections (servers and established)
304 Proto Recv-Q Send-Q Local Address Foreign Address State
305 tcp 0 0 *:echo *:* LISTEN
306 tcp 0 0 roke:echo gont:32981 ESTABLISHED
308 mentre per quanto riguarda l'esecuzione dei programmi avremo che:
310 \item il client chiama la funzione \code{ClientEcho} che si blocca sulla
311 \func{fgets} dato che non si è ancora scritto nulla sul terminale.
312 \item il server eseguirà una \func{fork} facendo chiamare al processo figlio
313 la funzione \code{ServEcho}, quest'ultima si bloccherà sulla \func{read}
314 dal socket sul quale ancora non sono presenti dati.
315 \item il processo padre del server chiamerà di nuovo \func{accept}
316 bloccandosi fino all'arrivo di un'altra connessione.
318 e se usiamo il comando \cmd{ps} per esaminare lo stato dei processi otterremo
319 un risultato del tipo:
321 [piccardi@roke piccardi]$ ps ax
322 PID TTY STAT TIME COMMAND
324 2356 pts/0 S 0:00 ./echod
325 2358 pts/1 S 0:00 ./echo 127.0.0.1
326 2359 pts/0 S 0:00 ./echod
328 (dove si sono cancellate le righe inutili) da cui si evidenzia la presenza di
329 tre processi, tutti in stato di \textit{sleep} (vedi
330 \tabref{tab:proc_proc_states}).
332 Se a questo punto si inizia a scrivere qualcosa sul client non sarà trasmesso
333 niente fin tanto che non si prema il tasto di a capo (si ricordi quanto detto
334 in \secref{sec:file_line_io} a proposito dell'I/O su terminale), solo allora
335 \func{fgets} ritornerà ed il client scriverà quanto immesso sul socket, per
336 poi passare a rileggere quanto gli viene inviato all'indietro dal server, che
337 a sua volta sarà inviato sullo standard output, che nel caso ne provoca
338 l'immediatamente stampa a video.
341 \subsection{La conclusione normale}
342 \label{sec:TCPsimpl_conclusion}
344 Tutto quello che scriveremo sul client sarà rimandato indietro dal server e
345 ristampato a video fintanto che non concluderemo l'immissione dei dati; una
346 sessione tipica sarà allora del tipo:
348 [piccardi@roke sources]$ ./echo 127.0.0.1
354 che termineremo inviando un EOF dal terminale (usando la combinazione di tasti
355 ctrl-D, che non compare a schermo); se eseguiamo un \cmd{netstat} a questo
358 [piccardi@roke piccardi]$ netstat -at
359 tcp 0 0 *:echo *:* LISTEN
360 tcp 0 0 localhost:33032 localhost:echo TIME_WAIT
362 con il client che entra in \texttt{TIME\_WAIT}.
364 Esaminiamo allora in dettaglio la sequenza di eventi che porta alla
365 terminazione normale della connessione, che ci servirà poi da riferimento
366 quando affronteremo il comportamento in caso di conclusioni anomale:
369 \item inviando un carattere di EOF da terminale la \func{fgets} ritorna
370 restituendo un puntatore nullo che causa l'uscita dal ciclo di
371 \code{while}, così la \code{ClientEcho} ritorna.
372 \item al ritorno di \code{ClientEcho} ritorna anche la funzione \code{main}, e
373 come parte del processo terminazione tutti i file descriptor vengono chiusi
374 (si ricordi quanto detto in \secref{sec:proc_term_conclusion}); questo causa
375 la chiusura del socket di comunicazione; il client allora invierà un FIN al
376 server a cui questo risponderà con un ACK. A questo punto il client verrà a
377 trovarsi nello stato \texttt{FIN\_WAIT\_2} ed il server nello stato
378 \texttt{CLOSE\_WAIT} (si riveda quanto spiegato in
379 \secref{sec:TCPel_conn_term}).
380 \item quando il server riceve il FIN la \func{read} del processo figlio che
381 gestisce la connessione ritorna restituendo 0 causando così l'uscita dal
382 ciclo e il ritorno di \code{ServEcho}, a questo punto il processo figlio
383 termina chiamando \func{exit}.
384 \item all'uscita del figlio tutti i file descriptor vengono chiusi, la
385 chiusura del socket connesso fa sì che venga effettuata la sequenza finale
386 di chiusura della connessione, viene emesso un FIN dal server che riceverà
387 un ACK dal client, a questo punto la connessione è conclusa e il client
388 resta nello stato \texttt{TIME\_WAIT}.
393 \subsection{La gestione dei processi figli}
394 \label{sec:TCPsimpl_child_hand}
396 Tutto questo riguarda la connessione, c'è però da tenere conto dell'effetto
397 del procedimento di chiusura del processo figlio nel server (si veda quanto
398 esaminato in \secref{sec:proc_termination}). In questo caso avremo l'invio del
399 segnale \macro{SIGCHLD} al padre, ma dato che non si è installato un
400 manipolatore e che l'azione predefinita per questo segnale è quella di essere
401 ignorato, non avendo predisposto la ricezione dello stato di terminazione,
402 otterremo che il processo figlio entrerà nello stato di zombie (si riveda
403 quanto illustrato in \secref{sec:sig_sigchld}), come risulterà ripetendo il
406 2356 pts/0 S 0:00 ./echod
407 2359 pts/0 Z 0:00 [echod <defunct>]
410 Poiché non è possibile lasciare processi zombie che pur inattivi occupano
411 spazio nella tabella dei processi e a lungo andare saturerebbero le risorse
412 del kernel, occorrerà ricevere opportunamente lo stato di terminazione del
413 processo (si veda \secref{sec:proc_wait}), cosa che faremo utilizzando
414 \macro{SIGCHLD} secondo quanto illustrato in \secref{sec:sig_sigchld}.
416 La prima modifica al nostro server è pertanto quella di inserire la gestione
417 della terminazione dei processi figli attraverso l'uso di un manipolatore.
418 Per questo useremo la funzione \code{Signal}, illustrata in
419 \figref{fig:sig_Signal_code}, per installare il semplice manipolatore che
420 riceve i segnali dei processi figli terminati già visto in
421 \figref{fig:sig_sigchld_handl}; aggiungendo il seguente codice:
424 /* install SIGCHLD handler */
425 Signal(SIGCHLD, sigchld_hand); /* establish handler */
431 all'esempio illustrato in \figref{fig:TCPsimpl_serv_code}, e linkando il tutto
432 alla funzione \code{sigchld\_hand}, si risolverà completamente il problema
439 %%% TeX-master: "gapil"