Aggiunta versione concorrente del server daytime. Iniziata la relativa
[gapil.git] / elemtcp.tex
index 9eab58a2adf71cb073b6562dd5c17e3740f31069..fd2d34a23c1951015357132c800a1c5a9e230356 100644 (file)
@@ -1,27 +1,28 @@
 \chapter{Socket TCP elementari}
 \label{cha:elem_TCP_sock}
 
-In questo capitolo inizieremo ad approndire la conoscenza dei socket TCP,
+In questo capitolo iniziamo ad approndire la conoscenza dei socket TCP,
 tratteremo qui dunque il funzionamento delle varie funzioni che si sono usate
 nei due esempi elementari forniti in precedenza (vedi
 \secref{sec:net_cli_sample} e \secref{sec:net_serv_sample}), previa una
 descrizione delle principali caratteristiche del funzionamento di una
 connessione TCP.
 
-La seconda parte del capitolo sarà poi dedicata alla scrittura di una prima
-semplice applicazione client/server completa, che implementi il servizio
-standard \texttt{echo} su TCP.
+Infine riscriveremo il precedente esempio elementare di server
+\texttt{daytime} in una forma appena più evoluta (come server concorrente) e
+con alcune caratteristiche aggiuntive che mettano in luce quanto andremo ad
+illustrare.
 
 \section{Il funzionamento di una connessione TCP}
 \label{sec:TCPel_connession}
 
 Prima di entrare nei dettagli delle funzioni usate nelle applicazioni che
 utilizzano i socket TCP, è fondamentale spiegare alcune basi del funzionamento
-del TCP, la conoscenza del funzionamento del protocollo è infatti essenziale
+del TCP; la conoscenza del funzionamento del protocollo è infatti essenziale
 per capire il modello di programmazione ed il funzionamento delle API.
 
-In particolare ci concentreremo sulle modalità con le quali il protocollo da
-inizio e conclude una connessione; faremo anche un breve accenno al
+In particolare ci concentreremo sulle modalità con le quali il protocollo dà
+inizio e conclude una connessione; faremo inoltre anche un breve accenno al
 significato di alcuni dei vari stati che il protocollo assume durante la vita
 di una connessione, che possono essere osservati per ciascun socket attivo con
 l'uso del programma \texttt{netstat}.
@@ -716,7 +717,7 @@ La funzione \texttt{connect} 
 connessione con un server TCP, il prototipo della funzione è il seguente:
 
 \begin{prototype}{sys/socket.h}
-{int connect(int sockfd, const struct sockaddr *serv\_addr, socklen\_t addrlen)}
+{int connect(int sockfd, const struct sockaddr *servaddr, socklen\_t addrlen)}
   
   Il primo argomento è un file descriptor ottenuto da una precedente chiamata
   a \texttt{socket}, mentre il secondo e terzo argomento sono rispettivamente
@@ -875,7 +876,7 @@ Storicamente il valore del parametro \texttt{backlog} era corrispondente al
 massimo valore della somma del numero di entrate possibili per ciascuna di
 dette code. Stevens riporta che BSD ha sempre applicato un fattore di 1.5 al
 valore, e provvede una tabella con i risultati ottenuti con vari kernel,
-compreso linux 2.0, che mostrano le differenze fra diverse implementazioni.
+compreso linux 2.0, che mostrano le differenze fra diverse implementazioni. 
 
 Ma in linux il significato di questo valore è cambiato a partire dal kernel
 2.2 per prevenire l'attacco chiamato \texttt{syn flood}. Questo si basa
@@ -892,7 +893,9 @@ la \texttt{sysctl} o scrivendola direttamente in
 \texttt{/proc/sys/net/ipv4/tcp\_max\_syn\_backlog}. Quando si attiva la
 protezione dei syncookies però (con l'opzione da compilare nel kernel e da
 attivare usando \texttt{/proc/sys/net/ipv4/tcp\_syncookies}) questo valore
-viene ignorato e non esiste più un valore massimo.
+viene ignorato e non esiste più un valore massimo.  In ogni caso in linux il
+valore di \texttt{backlog} viene troncato ad un massimo di \texttt{SOMAXCONN}
+se è superiore a detta constante (che di default vale 128).
 
 La scelta storica per il valore di questo parametro è di 5, e alcuni vecchi
 kernel non supportavano neanche valori superiori, ma la situazione corrente è
@@ -903,11 +906,11 @@ conviene specificare questo valore con una costante (il cui cambiamento
 richiederebbe la ricompilazione del server) ma usare piuttosto una variabile
 di ambiente (vedi \secref{sec:xxx_env_var}).  Lo Stevens tratta accuratamente
 questo argomento, con esempi presi da casi reali su web server, ed in
-particolare evidenzia come non sia più vero che la ragione della coda è quella
-di gestire il caso in cui il server è occupato fra chiamate successive alla
-\texttt{accept} (per cui la coda più occupata sarebbe quella delle connessioni
-compeltate), ma è invece necessaria a gestire la presenza di un gran numero di
-SYN in attesa di completare il three-way handshake.
+particolare evidenzia come non sia più vero che il compito principale della
+coda sia quello di gestire il caso in cui il server è occupato fra chiamate
+successive alla \texttt{accept} (per cui la coda più occupata sarebbe quella
+delle connessioni compeltate), ma piuttosto quello di gestire la presenza di
+un gran numero di SYN in attesa di completare il three-way handshake.
 
 Come accennato nel caso del TCP se un SYN arriva con tutte le code piene, il
 pacchetto sarà ignorato. Questo viene fatto perché la condizione delle code
@@ -919,7 +922,6 @@ lasciare la gestione della connessione alla ritrasmissione prevista dal
 protocollo TCP.
 
 
-
 \subsection{La funzione \texttt{accept}}
 \label{sec:TCPel_func_accept}
 
@@ -929,15 +931,14 @@ funzione restituisce un nuovo socket descriptor su cui si potr
 effettuare la comunicazione. Se non ci sono connessioni completate il processo
 viene messo in attesa. Il prototipo della funzione è il seguente:
 
-
 \begin{prototype}{sys/socket.h}
-{int listen(int sockfd, struct sockaddr *addr, socklen\_t *addrlen)}  
-  La funzione estrae la prima connessione completa relativa al socket
-  \texttt{sockfd} in attesa sulla coda delle connessioni complete che associa
-  nuovo socket con le stesse caratteristiche di \texttt{sockfd} (restituito
-  dalla funzione stessa). Il socket originale non viene toccato. Nella
-  struttura \texttt{addr} e nella variabile \texttt{addrlen} vengono
-  restituiti indirizzo e relativa lunghezza del client che si è connesso.
+{int listen(int sockfd, struct sockaddr *addr, socklen\_t *addrlen)} 
+  La funzione estrae la prima connessione relativa al socket \texttt{sockfd}
+  in attesa sulla coda delle connessioni complete, che associa ad nuovo socket
+  con le stesse caratteristiche di \texttt{sockfd} (restituito dalla funzione
+  stessa).  Il socket originale non viene toccato. Nella struttura
+  \texttt{addr} e nella variabile \texttt{addrlen} vengono restituiti
+  indirizzo e relativa lunghezza del client che si è connesso.
  
   La funzione restituisce un numero di socket descriptor positivo in caso di
   successo e -1 in caso di errore, nel qual caso la variabile \texttt{errno}
@@ -959,6 +960,13 @@ viene messo in attesa. Il prototipo della funzione 
   \item \texttt{ENOBUFS, ENOMEM} Not enough free memory.  This often means
     that the memory allocation is limited by the socket buffer limits, not by
     the system memory.
+    Inoltre possono essere restituiti gli errori di rete relativi al nuovo
+    socket come: \texttt{EMFILE}, \texttt{EINVAL}, \texttt{ENOSR},
+    \texttt{ENOBUFS}, \texttt{EPERM}, \texttt{ECONNABORTED},
+    \texttt{ESOCKTNOSUPPORT}, \texttt{EPROTONOSUPPORT}, \texttt{ETIMEDOUT},
+    \texttt{ERESTARTSYS}.
+
   \end{errlist}
 \end{prototype}
 
@@ -971,26 +979,131 @@ connessioni, la conferma della connessione viene fatta implicitamente dalla
 prima chiamata ad una \texttt{read} o una \texttt{write} mentre il rifiuto
 della connessione viene fatta con la funzione \texttt{close}.
 
+E da chiarire che linux presenta un comportamento diverso nella gestione degli
+errori rispetto ad altre implementazioni dei socket BSD, infatti la funzione
+\texttt{accept} passa gli errori di rete pendenti sul nuovo socket come codici
+di errore per \texttt{accept}. Inoltre la funzione non fa ereditare ai nuovi
+socket flag come \texttt{O\_NONBLOCK}, che devono essere rispecificati volta
+volta, questo è un comportamento diverso rispetto a quanto accade con BSD e
+deve essere tenuto in conto per scrivere programmi portabili.
+
 I due parametri \texttt{cliaddr} e \texttt{addrlen} (si noti che quest'ultimo
 è passato per indirizzo per avere indietro il valore) sono usati per ottenere
 l'indirizzo del client da cui proviene la connessione. Prima della chiamata
 \texttt{addrlen} deve essere inizializzato alle dimensioni della struttura il
-cui indirizzo è passato come parametro in \texttt{cliaddr}, al rientro della
+cui indirizzo è passato come parametro in \texttt{cliaddr}, al ritorno della
 funzione \texttt{addrlen} conterrà il numero di bytes scritti dentro
-\texttt{cliaddr}.
-
-Se la funzione ha successo restituisce un nuovo socket descriptor, detto
-\textit{connected socket}, su cui è agganciata la connessione che il client
-TCP ha effettuato verso il socket \texttt{sockfd}. Quest'ultimo, che viene
-chiamato invece \textit{listening socket}, deve essere stato creato in
-precedenza e messo in ascolto con \texttt{listen}, e non viene toccato dalla
-funzione. 
-
-Questa distinzione è essenziale per capire 
-
+\texttt{cliaddr}. Se questa informazione non interessa basterà inizializzare a
+\texttt{NULL} detti puntatori.
+
+Se la funzione ha successo restituisce il descrittore di un nuovo socket
+creato dal kernel (detto \textit{connected socket}) a cui viene associata la
+prima connessione completa (estratta dalla relativa coda, vedi
+\secref{sec:TCPel_func_listen}) che il client TCP ha effettuato verso il
+socket \texttt{sockfd}. Quest'ultimo (detto \textit{listening socket}) è
+quello creato all'inizio e messo in ascolto con \texttt{listen}, e non viene
+toccato dalla funzione.  
+
+Se non ci sono connessioni pendenti da accettare la funzione mette in attesa
+il processo\footnote{a meno che non si sia settato il socket per essere
+  non-bloccante, nel qual caso ritorna con l'errore \texttt{EAGAIN},
+  torneremo su questa modalità di operazione in \secref{sec:xxx_sock_noblock}}
+fintanto che non ne arriva una.
+Questo meccanismo è essenziale per capire il funzionamento di un server, in
+generale infatti c'è sempre un solo socket in ascolto, che resta per tutto il
+tempo nello stato \texttt{LISTEN}, mentre le connessioni vengono gestite dai
+nuovi socket ritornati da \texttt{accept} che sono posti automaticamente nello
+stato \texttt{ESTABLISHED} e utilizzati fino alla chiusura della connessione
+che avviene su di essi.  Si può riconoscere questo schema anche nell'esempio
+elementare in \figref{fig:net_serv_code} dove per ogni connessione il socket
+creato da \texttt{accept} viene chiuso dopo l'invio dei dati.
+
+
+\section{Un server concorrente su TCP}
+\label{sec:TCPel_cunc_serv}
+
+Il server \texttt{daytime} dell'esempio in \secref{sec:net_cli_sample} è 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
+bloccare un server nel servizio di un client per volta; per questo si ricorre
+alle capacità di multitasking del sistema.
+
+Il modo più immediato per creare un server concorrente è allora quello di
+usare la funzione \texttt{fork} per far creare al server per ogni richiesta da
+parte di un client un processo figlio che si incarichi della gestione della
+comunicazione.
+
+Per illustrare questo meccanismo abbiamo allora riscritto il server
+\texttt{daytime} in forma concorrente, inserendo anche una opzione per la
+stampa degli indirizzi delle connessioni ricevute.
+
+In \nfig\ è mostrato un estratto del codice, in cui si sono tralasciate il
+trattamento delle opzioni e le parti rimaste invariate rispetto al precedente
+esempio. Al solito il sorgente completo del server
+\texttt{ElemDaytimeTCPCuncServ.c} è allegato nella directory dei sorgenti.
 
+\begin{figure}[!htb]
+  \footnotesize
+  \begin{lstlisting}{}
+#include <sys/types.h>   /* predefined types */
+#include <unistd.h>      /* include unix standard library */
+#include <arpa/inet.h>   /* IP addresses conversion utiliites */
+#include <sys/socket.h>  /* socket library */
+#include <stdio.h>       /* include standard I/O library */
+#include <time.h>
+
+int main(int argc, char *argv[])
+{
+    int list_fd, conn_fd;
+    int i;
+    struct sockaddr_in serv_add, client;
+    char buffer[MAXLINE];
+    socklen_t len;
+    time_t timeval;
+    pid_t pid;
+    int logging=0;
+     ...
+    /* write daytime to client */
+    while (1) {
+        if ( (conn_fd = accept(list_fd, (struct sockaddr *)&client, &len)) 
+             <0 ) {
+            perror("accept error");
+            exit(-1);
+        }
+        /* fork to handle connection */
+        if ( (pid = fork()) < 0 ){
+            perror("fork error");
+            exit(-1);
+        }
+        if (pid == 0) {                 /* child */
+            close(list_fd);
+            timeval = time(NULL);
+            snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&timeval));
+            if ( (write(conn_fd, buffer, strlen(buffer))) < 0 ) {
+                perror("write error");
+                exit(-1);
+            }
+            if (logging) {
+                inet_ntop(AF_INET, &client.sin_addr, buffer, sizeof(buffer));
+                printf("Request from host %s, port %d\n", buffer,
+                       ntohs(client.sin_port));
+            }
+            close(conn_fd);
+            exit(0);
+        } else {                        /* parent */
+            close(conn_fd);
+        }
+    }
+    /* normal exit, never reached */
+    exit(0);
+}
+  \end{lstlisting}
+  \caption{Esempio di codice di un server concorrente elementare per il 
+    servizio daytime.}
+  \label{fig:net_cli_code}
+\end{figure}
 
-\section{Una semplice implementazione del servizio \texttt{echo} su TCP}
-\label{sec:TCPel_echo_example}
+Come si può vedere (\texttt{\small 21--25}) alla funzione \texttt{accept} 
 
-Veniamo ora ad una applicazione