Sistemati client e server echo
[gapil.git] / simpltcp.tex
1 \chapter{Un esempio completo di client/server TCP}
2 \label{cha:simple_TCP_sock}
3
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. 
7
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 una applicazione di rete.
12
13
14 \section{Il servizio \texttt{echo}}
15 \label{sec:TCPsimp_echo}
16
17 L'applicazione scelta come esempio sarà una implementazione elementare, ma
18 completa, del servizio \texttt{echo}. Il servizio \texttt{echo} è uno dei
19 servizi standard solitamente provvisti direttamente dal superserver
20 \texttt{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
25 opera sulla porta 7.
26
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.
31
32 Si è scelto di usare questo servizio, seguendo lo Stevens, perché costituisce
33 il prototipo ideale di una generica applicazione di rete in cui un server
34 risponde alle richieste di un client; tutto quello che cambia nel caso si una
35 applicazione più complessa è la elaborazione dell'input del client da parte
36 del server nel fornire le risposte in uscita. 
37
38 \subsection{La struttura del server}
39 \label{sec:TCPsimp_server_main}
40
41 Il server si compone di un corpo principale, costituito dalla funzione
42 \texttt{main} che si incarica di creare il socket, metterlo in ascolto di
43 connessioni in arrivo e creare un processo figlio a cui delegare la gestione
44 di ciascuna connessione. Questa parte, riportata in \nfig, è sostanzialmente
45 identica a quella vista nell'esempio in \secref{sec:TCPelem_serv_code}.
46
47 \begin{figure}[!htb]
48   \footnotesize
49   \begin{lstlisting}{}
50 /* Subroutines declaration */
51 void SockEcho(int sockfd);
52 /* Program beginning */
53 int main(int argc, char *argv[])
54 {
55     int list_fd, conn_fd;
56     pid_t pid;
57     struct sockaddr_in serv_add;
58      ....
59     /* create socket */
60     if ( (list_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
61         perror("Socket creation error");
62         exit(-1);
63     }
64     /* initialize address */
65     memset((void *)&serv_add, 0, sizeof(serv_add)); /* clear server address */
66     serv_add.sin_family = AF_INET;                  /* address type is INET */
67     serv_add.sin_port = htons(13);                  /* daytime port is 13 */
68     serv_add.sin_addr.s_addr = htonl(INADDR_ANY);   /* connect from anywhere */
69     /* bind socket */
70     if (bind(list_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
71         perror("bind error");
72         exit(-1);
73     }
74     /* listen on socket */
75     if (listen(list_fd, BACKLOG) < 0 ) {
76         perror("listen error");
77         exit(-1);
78     }
79     /* handle echo to client */
80     while (1) {
81         /* accept connection */
82         if ( (conn_fd = accept(list_fd, NULL, NULL)) < 0) {
83             perror("accept error");
84             exit(-1);
85         }
86         /* fork to handle connection */
87         if ( (pid = fork()) < 0 ){
88             perror("fork error");
89             exit(-1);
90         }
91         if (pid == 0) {      /* child */
92             close(list_fd);          /* close listening socket */   
93             SockEcho(conn_fd);       /* handle echo */
94             exit(0);
95         } else {             /* parent */
96             close(conn_fd);          /* close connected socket */
97         }
98     }
99     /* normal exit, never reached */
100     exit(0);
101 }
102   \end{lstlisting}
103   \caption{Codice della funzione \texttt{main} della prima versione del server
104     per il servizio \texttt{echo}.}
105   \label{fig:TCPsimpl_serv_code}
106 \end{figure}
107
108 La struttura di questa prima versione del server è sostanzialmente a quella
109 dell'esempio precedente, ed ad esso si applicano le considerazioni fatte in
110 \secref{sec:TCPel_cunc_daytime}. Le uniche differenze rispetto all'esempio in
111 \figref{fig:TCPelem_serv_code} sono che in questo caso per il socket in
112 ascolto viene usata la porta 7 e tutta la gestione della comunicazione è
113 delegata alla funzione \texttt{SockEcho}. Per ogni connessione viene creato un
114 processo figlio, il quale si incarica di lanciare la funzione
115 \texttt{SockEcho}.
116
117 Il codice della funzione \texttt{SockEcho} è invece mostrata in \nfig, la
118 comunicazione viene gestita all'interno del ciclo (linee \texttt{\small
119   6--8}).  I dati inviati dal client vengono letti dal socket con una semplice
120 \texttt{read} (che ritorna solo in presenza di dati in arrivo), la riscrittura
121 viene invece gestita dalla funzione \texttt{SockWrite} (descritta a suo tempo
122 in \figref{fig:sock_SockWrite_code}) che si incarica di tenere conto
123 automaticamente della possibilità che non tutti i dati di cui è richiesta la
124 scrittura vengano trasmessi con una singola \texttt{write}.
125
126 Quando il client chiude la connessione il ricevimento del FIN fa ritornare la
127 \texttt{read} con un numero di byte letti pari a zero, il che causa l'uscita
128 dal ciclo e il ritorno della funzione, che a sua volta causa la terminazione
129 del processo figlio.
130
131
132 \begin{figure}[!htb]
133   \footnotesize
134   \begin{lstlisting}{}
135 void SockEcho(int sockfd) {
136     char buffer[MAXLINE];
137     int nread, nwrite;
138     
139     /* main loop, reading 0 char means client close connection */
140     while ( (nread = read(sockfd, buffer, MAXLINE)) != 0) {
141         nwrite = SockWrite(sockfd, buffer, nread);
142     }
143     return;
144 }
145   \end{lstlisting}
146   \caption{Codice della prima versione della funzione \texttt{SockEcho} per la
147     gestione del servizio \texttt{echo}.}
148   \label{fig:TCPsimpl_sockecho_code}
149 \end{figure}
150
151
152 \subsection{Il client}
153 \label{sec:TCPsimp_server_main}
154
155 Il codice del client è riportato in \nfig, anche esso ricalca la struttura del
156 precedente client per il servizio \texttt{daytime} (vedi
157 \secref{sec:net_cli_sample}) ma, come per il server, lo si è diviso in due
158 parti, inserendo la parte relativa alle operazioni specifiche previste per il
159 protocollo \texttt{echo} in una funzione a parte.
160 \begin{figure}[!htb]
161   \footnotesize
162   \begin{lstlisting}{}
163 int main(int argc, char *argv[])
164 {
165 /* 
166  * Variables definition  
167  */
168     int sock_fd, i;
169     struct sockaddr_in serv_add;
170     ...
171     /* create socket */
172     if ( (sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
173         perror("Socket creation error");
174         return -1;
175     }
176     /* initialize address */
177     memset((void *) &serv_add, 0, sizeof(serv_add)); /* clear server address */
178     serv_add.sin_family = AF_INET;                   /* address type is INET */
179     serv_add.sin_port = htons(7);                    /* echo port is 7 */
180     /* build address using inet_pton */
181     if ( (inet_pton(AF_INET, argv[optind], &serv_add.sin_addr)) <= 0) {
182         perror("Address creation error");
183         return -1;
184     }
185     /* extablish connection */
186     if (connect(sock_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
187         perror("Connection error");
188         return -1;
189     }
190     /* read daytime from server */
191     EchoClient(stdin, sock_fd);
192     /* normal exit */
193     return 0;
194 }
195   \end{lstlisting}
196   \caption{Codice della prima versione del client \texttt{echo}.}
197   \label{fig:TCPsimpl_sockecho_code}
198 \end{figure}
199
200 La funzione \texttt{main} si occupa della creazione del socket e della
201 connessione (linee \texttt{\small 10--27}) secondo la stessa modalità spiegata
202 in \secref{sec:net_cli_sample}, il client si connette sulla porta 7
203 all'indirizzo specificato dalla linea di comando (a cui si è aggiunta una
204 elementare gestione delle opzioni non riportata in figura).
205
206 Completata la connessione (quando la funzione \texttt{connect} ritorna) La
207 funzione \texttt{EchoClient}, riportata in \nfig, si preoccupa di gestire la
208 comunicazione, leggendo una riga alla volta dallo \texttt{stdin}, scrivendola
209 sul socket e ristampando su \texttt{stdout} quanto ricevuto in risposta dal
210 server. 
211
212 \begin{figure}[!htb]
213   \footnotesize
214   \begin{lstlisting}{}
215 void EchoClient(FILE * filein, int socket) 
216 {
217     char sendbuff[MAXLINE], recvbuff[MAXLINE];
218     int nread; 
219     while (fgets(sendbuff, MAXLINE, filein) != NULL) {
220         SockWrite(socket, sendbuff, strlen(sendbuff)); 
221         nread = SockRead(socket, recvbuff, strlen(sendbuff));        
222         recvbuff[nread] = 0;
223         fputs(recvbuff, stdout);
224     }
225     return;
226 }
227   \end{lstlisting}
228   \caption{Codice della prima versione della funzione \texttt{EchoClient} per 
229     la gestione del servizio \texttt{echo}.}
230   \label{fig:TCPsimpl_sockecho_code}
231 \end{figure}
232
233 La funzione utilizza due buffer per gestire i dati inviati e letti sul socket
234 (\texttt{\small 3}).  La comunicazione viene gestita all'interno di un ciclo
235 (linee \texttt{\small 5--10}), i dati da inviare sulla connessione vengono
236 presi dallo \texttt{stdin} usando la funzione \texttt{fgets} che legge una
237 linea di testo (terminata da un \texttt{CR} e fino al massimo di
238 \texttt{MAXLINE} caratteri) e la salva sul buffer di invio, la funzione
239 \texttt{SockWrite} (\texttt{\small 3}) scrive detti dati sul socket (gestendo
240 l'invio multiplo, qualora una singola \texttt{write} non basta, come spiegato
241 in \secref{sec:sock_io_behav}).
242
243 I dati che vengono riletti indietro con una \texttt{SockRead} sul buffer di
244 ricezione e viene inserita la terminazione della stringa (\texttt{\small
245   7--8}) e per poter usare la funzione \texttt{fputs} per scriverli su
246 \texttt{stdout}. 
247
248 Un end of file inviato su \texttt{stdin} causa il ritorno di \texttt{fgets}
249 con un puntatore nullo e l'uscita dal ciclo, al che la subroutine ritorna ed
250 il client esce.
251
252
253 \section{Il funzionamento del servizio}
254 \label{sec:TCPsimpl_normal_work}
255
256 Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà
257 di considerare in dettaglio tutte le problematiche che si possono incontrare
258 nello scrivere una applicazione di rete; infatti attraverso l'esame delle sue
259 modalità di funzionamento normali, all'avvio e alla terminazione, e di quello
260 che avviene nelle varie situazioni limite da una parte potremo approfondire la
261 comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni
262 necessarie per essere in gradi di scrivere applicazioni robuste, in grado di
263 gestire anche i casi limite.
264
265
266 \subsection{L'avvio e il funzionamento}
267 \label{sec:TCPsimpl_startup}
268
269 Il primo passo è compilare e lanciare il server (da root, per poter usare la
270 porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con
271 la sequenza delle chiamate a \texttt{socket}, \texttt{bind}, \texttt{listen} e
272 poi si bloccherà nella \texttt{accept}. A questo punto si potrà controllarne
273 lo stato con \texttt{netstat}:
274
275 \begin{verbatim}
276 [piccardi@roke piccardi]$ netstat -ant
277 Active Internet connections (servers and established)
278 Proto Recv-Q Send-Q Local Address           Foreign Address         State 
279 ...
280 tcp        0      0 *:echo                  *:*                     LISTEN
281 ...
282 \end{verbatim} %$
283
284 che ci mostra come il socket sia in ascolto sulla porta richiesta, accendo
285 connessioni da qualunque indirizzo e da qualunque porta e su qualunque
286 interfaccia locale.
287
288 A questo punto si può lanciare il client, esso chiamerà \texttt{socket} e
289 \texttt{connect}, una volta completato il three way handshake la funzione
290 \texttt{connect} ritornerà nel client e la \texttt{accept} nel server e la
291 connessione è stabilita, usando di nuovo \texttt{netstat} otterremmo:
292 \begin{verbatim}
293 Active Internet connections (servers and established)
294 Proto Recv-Q Send-Q Local Address           Foreign Address         State
295 tcp        0      0 *:echo                  *:*                     LISTEN
296 tcp        0      0 roke:echo               gont:32981              ESTABLISHED
297 \end{verbatim}
298
299 A questo punto  lo stato è il seguente: 
300
301
302 \begin{itemize}
303 \item il client chiama la funzione \texttt{EchoClient} che si blocca sulla
304   \texttt{fgets} dato che non si è ancora scritto nulla sul terminale.
305 \item il server eseguirà una \texttt{fork} facendo chiamare al processo figlo
306   la funzione \texttt{SockEcho}, quest'ultima si bloccherà sulla \texttt{read}
307   dal socket sul quale ancora non sono presenti dati.
308 \item il 
309 \end{itemize}
310 il server eseguira una \texttt{fork} facendo chiamare al
311 processo figlo la funzione \texttt{SockEcho}, la quale eseguirà una read s
312
313
314
315