Inserito esempio di server echo basato su select.
authorSimone Piccardi <piccardi@gnulinux.it>
Thu, 25 Dec 2003 22:16:06 +0000 (22:16 +0000)
committerSimone Piccardi <piccardi@gnulinux.it>
Thu, 25 Dec 2003 22:16:06 +0000 (22:16 +0000)
sources/Makefile
sources/select_echod.c
tcpsockadv.tex

index 151d77922d25e4321f67961a6d1d10cf56412644..b669397516a55c7499de6dde860c37af37978caf 100644 (file)
@@ -3,7 +3,7 @@
 #
 # C flags
 CC=gcc
 #
 # C flags
 CC=gcc
-CFLAGS= -Wall -g -DDEBUG -fPIC
+CFLAGS= -Wall -g -fPIC #-DDEBUG
 CFLAGJ= -L./ -lgapil
 
 LIB = libgapil.so
 CFLAGJ= -L./ -lgapil
 
 LIB = libgapil.so
index b1b64832693e59656d38ae21177770227e678839..3a0690bb5b283bfc5acbc02ffcc09e67f49d9a33 100644 (file)
@@ -26,7 +26,7 @@
  *
  * Usage: echod -h give all info
  *
  *
  * Usage: echod -h give all info
  *
- * $Id: select_echod.c,v 1.1 2003/12/25 17:31:09 piccardi Exp $ 
+ * $Id: select_echod.c,v 1.2 2003/12/25 22:16:06 piccardi Exp $ 
  *
  ****************************************************************/
 /* 
  *
  ****************************************************************/
 /* 
@@ -44,6 +44,7 @@
 #include <string.h>      /* error strings */
 #include <stdlib.h>
 
 #include <string.h>      /* error strings */
 #include <stdlib.h>
 
+#include "macros.h"
 #include "Gapil.h"
 
 #define BACKLOG 10
 #include "Gapil.h"
 
 #define BACKLOG 10
@@ -61,7 +62,7 @@ int main(int argc, char *argv[])
  */
     int waiting = 0;
     int compat = 0;
  */
     int waiting = 0;
     int compat = 0;
-    struct sockaddr_in serv_add, cli_add;
+    struct sockaddr_in s_addr, c_addr;
     socklen_t len;
     char buffer[MAXLINE];
     char fd_open[FD_SETSIZE];
     socklen_t len;
     char buffer[MAXLINE];
     char fd_open[FD_SETSIZE];
@@ -122,12 +123,12 @@ int main(int argc, char *argv[])
        exit(1);
     }
     /* initialize address */
        exit(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(7);                   /* echo port is 7 */
-    serv_add.sin_addr.s_addr = htonl(INADDR_ANY);   /* connect from anywhere */
+    memset((void *)&s_addr, 0, sizeof(s_addr));   /* clear server address */
+    s_addr.sin_family = AF_INET;                  /* address type is INET */
+    s_addr.sin_port = htons(7);                   /* echo port is 7 */
+    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* connect from anywhere */
     /* bind socket */
     /* bind socket */
-    if (bind(list_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) {
+    if (bind(list_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0) {
        perror("bind error");
        exit(1);
     }
        perror("bind error");
        exit(1);
     }
@@ -155,63 +156,67 @@ int main(int argc, char *argv[])
     if (waiting) sleep(waiting);
     /* initialize all needed variables */
     memset(fd_open, 0, FD_SETSIZE);   /* clear array of open files */
     if (waiting) sleep(waiting);
     /* initialize all needed variables */
     memset(fd_open, 0, FD_SETSIZE);   /* clear array of open files */
-    max_fd = list_fd;                 /* set maximum fd to look */
+    max_fd = list_fd;                 /* maximum now is listening socket */
     fd_open[max_fd] = 1;
     /* main loop, wait for connection and data inside a select */
     while (1) {    
        FD_ZERO(&fset);               /* clear fd_set */
     fd_open[max_fd] = 1;
     /* main loop, wait for connection and data inside a select */
     while (1) {    
        FD_ZERO(&fset);               /* clear fd_set */
-       for (i = list_fd; i <= max_fd; i++) {
-           if (fd_open[i]) FD_SET(i, &fset); /* initialize open sockets */
+       for (i = list_fd; i <= max_fd; i++) { /* initialize fd_set */
+           if (fd_open[i] != 0) FD_SET(i, &fset); 
        }
        while ( ((n = select(max_fd + 1, &fset, NULL, NULL, NULL)) < 0) 
        }
        while ( ((n = select(max_fd + 1, &fset, NULL, NULL, NULL)) < 0) 
-               && (errno == EINTR));
-       /* there are data */
-       if (n < 0) {
+               && (errno == EINTR));         /* wait for data or connection */
+       if (n < 0) {                          /* on real error exit */
            PrintErr("select error");
            exit(1);
            PrintErr("select error");
            exit(1);
-       }       
-       if (FD_ISSET(list_fd, &fset)) { /* if data on listening socket */
-           len = sizeof(cli_add);
-           fd = accept(list_fd, (struct sockaddr *)&cli_add, &len); 
-           if (fd < 0) {
+       }
+       /* on activity */
+       debug("Trovati %d socket attivi\n", n);
+       if (FD_ISSET(list_fd, &fset)) {       /* if new connection */
+           n--;                              /* decrement active */
+           len = sizeof(c_addr);             /* and call accept */
+           if ((fd = accept(list_fd, (struct sockaddr *)&c_addr, &len)) < 0) {
                PrintErr("accept error");
                exit(1);
            }
                PrintErr("accept error");
                exit(1);
            }
-           fd_open[fd] = 1;
-           if (max_fd < fd) max_fd = fd;
-           FD_SET(fd, &fset);
-           n--;
+           debug("Connessione su fd %d restano %d socket attivi\n", fd, n);
+           fd_open[fd] = 1;                  /* set new connection socket */
+           if (max_fd < fd) max_fd = fd;     /* if needed set new maximum */
+           debug("max_fd=%d\n", max_fd);
        }
        }
-       for (i = list_fd + 1; i <= max_fd; i++) {
-           if (fd_open[i] == 0) continue;
-           if (FD_ISSET(i, &fset)) {
-               n--;
-               nread = read(fd, buffer, MAXLINE);
+       /* loop on open connections */
+       i = list_fd;                  /* first socket to look */
+       while (n != 0) {              /* loop until active */
+           i++;                      /* start after listening socket */
+           debug("restano %d socket, fd %d\n", n, fd);
+           if (fd_open[i] == 0) continue;   /* closed, go next */
+           if (FD_ISSET(i, &fset)) {        /* if active process it*/
+               n--;                         /* decrease active */
+               debug("dati su fd %d\n", i);
+               nread = read(i, buffer, MAXLINE);     /* read operations */
                if (nread < 0) {
                    PrintErr("Errore in lettura");
                    exit(1);
                }
                if (nread < 0) {
                    PrintErr("Errore in lettura");
                    exit(1);
                }
-               if (nread == 0) {
-                   fd_open[i] = 0;
-                   if (max_fd == i) {
-                       while (fd_open[i] == 0) {
-                           i--;
-                       }
-                       max_fd = i;
-                       break;
+               if (nread == 0) {            /* if closed connection */
+                   debug("fd %d chiuso\n", i);
+                   close(i);                /* close file */
+                   fd_open[i] = 0;          /* mark as closed in table */
+                   if (max_fd == i) {       /* if was the maximum */
+                       while (fd_open[--i] == 0);    /* loop down */
+                       max_fd = i;          /* set new maximum */
+                       debug("nuovo max_fd %d\n", max_fd);
+                       break;               /* and go back to select */
                    }
                    }
-                   continue;
+                   continue;                /* continue loop on open */
                }
                }
-               nwrite = FullWrite(fd, buffer, nread);
+               nwrite = FullWrite(i, buffer, nread); /* write data */
                if (nwrite) {
                    PrintErr("Errore in scrittura");
                    exit(1);
                }
            }
        }
                if (nwrite) {
                    PrintErr("Errore in scrittura");
                    exit(1);
                }
            }
        }
-       if (n != 0) {
-           PrintErr("Errore nella gestione dei file descriptor");
-       }
     }
     /* normal exit, never reached */
     exit(0);
     }
     /* normal exit, never reached */
     exit(0);
index d2e58fe5c0e123eaf7d4379458442e8679fef3f1..7351738281d804bdd6762a143c2b2c93ced5cb26 100644 (file)
@@ -416,31 +416,31 @@ velocit
 una macchina remota occorre un certo tempo perché i pacchetti vi arrivino,
 vengano processati, e poi tornino indietro. Considerando trascurabile il tempo
 di processo, questo tempo è quello impiegato nella trasmissione via rete, che
 una macchina remota occorre un certo tempo perché i pacchetti vi arrivino,
 vengano processati, e poi tornino indietro. Considerando trascurabile il tempo
 di processo, questo tempo è quello impiegato nella trasmissione via rete, che
-viene detto RTT (da \textit{Round Trip Time}, e può essere stimato con l'uso
-del comando \cmd{ping}. 
+viene detto RTT (dalla denominazione inglese \textit{Round Trip Time}) ed è
+quello che viene stimato con l'uso del comando \cmd{ping}.
 
 A questo punto, se torniamo al codice mostrato in
 \figref{fig:TCP_ClientEcho_third}, possiamo vedere che mentre i pacchetti sono
 in transito sulla rete il client continua a leggere e a scrivere fintanto che
 
 A questo punto, se torniamo al codice mostrato in
 \figref{fig:TCP_ClientEcho_third}, possiamo vedere che mentre i pacchetti sono
 in transito sulla rete il client continua a leggere e a scrivere fintanto che
-il file in ingresso finisce. Però che non appena viene ricevuto un end-of-file
-in ingresso il nostro client termina. Nel caso interattivo, in cui si
-inviavano brevi stringhe una alla volta, c'era sempre il tempo di eseguire la
-lettura completa di quanto il server rimandava indietro. In questo caso
-invece, quando il client termina, essendo la comunicazione saturata e a piena
-velocità, ci saranno ancora pacchetti in transito sulla rete che devono
-arrivare al server e poi tornare indietro, ma siccome il client esce
-immediatamente dopo la fine del file in ingresso, questi non faranno a tempo a
-completare il percorso e verranno persi.
-
-Per evitare questo tipo di problema, invece di uscire occorre usare
-\func{shutdown} per effettuare la chiusura del lato in scrittura del socket,
-una volta completata la lettura del file in ingresso. In questo modo il client
-segnalerà al server la chiusura del flusso dei dati, ma potrà continuare a
-leggere quanto il server gli sta ancora inviando indietro fino a quando anche
-lui, riconosciuta la chiusura del socket in scrittura da parte del client,
+il file in ingresso finisce. Però non appena viene ricevuto un end-of-file in
+ingresso il nostro client termina. Nel caso interattivo, in cui si inviavano
+brevi stringhe una alla volta, c'era sempre il tempo di eseguire la lettura
+completa di quanto il server rimandava indietro. In questo caso invece, quando
+il client termina, essendo la comunicazione saturata e a piena velocità, ci
+saranno ancora pacchetti in transito sulla rete che devono arrivare al server
+e poi tornare indietro, ma siccome il client esce immediatamente dopo la fine
+del file in ingresso, questi non faranno a tempo a completare il percorso e
+verranno persi.
+
+Per evitare questo tipo di problema, invece di uscire una volta completata la
+lettura del file in ingresso, occorre usare \func{shutdown} per effettuare la
+chiusura del lato in scrittura del socket. In questo modo il client segnalerà
+al server la chiusura del flusso dei dati, ma potrà continuare a leggere
+quanto il server gli sta ancora inviando indietro, fino a quando anch'esso,
+riconosciuta la chiusura del socket in scrittura da parte del client,
 effettuerà la chiusura dalla sua parte. Solo alla ricezione della chiusura del
 effettuerà la chiusura dalla sua parte. Solo alla ricezione della chiusura del
-socket da parte del server si potrà essere sicuri della ricezione di tutti i
-dati e della terminazione effettiva della connessione.
+socket da parte del server il client potrà essere sicuro della ricezione di
+tutti i dati e della terminazione effettiva della connessione.
 
 \begin{figure}[!htb]
   \footnotesize \centering
 
 \begin{figure}[!htb]
   \footnotesize \centering
@@ -510,15 +510,15 @@ Seguendo di nuovo le orme di Stevens in \cite{UNP1} vediamo ora come con
 l'utilizzo dell'I/O multiplexing diventi possibile riscrivere completamente il
 nostro server \textit{echo} con una architettura completamente diversa, in
 modo da evitare di dover creare un nuovo processo tutte le volte che si ha una
 l'utilizzo dell'I/O multiplexing diventi possibile riscrivere completamente il
 nostro server \textit{echo} con una architettura completamente diversa, in
 modo da evitare di dover creare un nuovo processo tutte le volte che si ha una
-connessione.
+connessione.\footnote{ne faremo comunque una implementazione diversa rispetto
+  a quella presentata da Stevens in \cite{UNP1}.}
 
 La struttura del nuovo server è illustrata in \figref{fig:TCP_echo_multiplex},
 in questo caso avremo un solo processo che ad ogni nuova connessione da parte
 
 La struttura del nuovo server è illustrata in \figref{fig:TCP_echo_multiplex},
 in questo caso avremo un solo processo che ad ogni nuova connessione da parte
-del client sul socket in ascolto inserirà il socket connesso ad essa relativo
-in una opportuna tabella, poi utilizzerà \func{select} per rilevare la
-presenza di dati in arrivo su ciascun socket connesso, riutilizzandolo per
-inviare i dati in risposta.
-
+di un client sul socket in ascolto si limiterà a registrare l'entrata in uso
+di un nuovo file descriptor ed utilizzerà \func{select} per rilevare la
+presenza di dati in arrivo su tutti i file descriptor attivi, operando
+direttamente su ciascuno di essi.
 
 \begin{figure}[htb]
   \centering
 
 \begin{figure}[htb]
   \centering
@@ -527,6 +527,141 @@ inviare i dati in risposta.
   \label{fig:TCP_echo_multiplex}
 \end{figure}
 
   \label{fig:TCP_echo_multiplex}
 \end{figure}
 
+La sezione principale del codice del nuovo server è illustrata in
+\figref{fig:TCP_SelectEchod}. Si è tralasciata al solito la gestione delle
+opzioni, che è identica alla versione precedente. Resta invariata anche tutta
+la parte relativa alla gestione dei segnali, degli errori, e della cessione
+dei privilegi, così come è identica la gestione della creazione del socket (si
+può fare riferimento al codice già illustrato in
+\secref{sec:TCPsimp_server_main}); al solito il codice completo del server è
+disponibile coi sorgenti allegati nel file \texttt{select\_echod.c}.
+
+\begin{figure}[!htbp]
+  \footnotesize \centering
+  \begin{minipage}[c]{15.6cm}
+    \includecodesample{listati/select_echod.c}
+  \end{minipage} 
+  \normalsize
+  \caption{La sezione principale del codice della nuova versione di server
+    \textit{echo} basati sull'uso della funzione \func{select}.}
+  \label{fig:TCP_SelectEchod}
+\end{figure}
+
+In questo caso, una volta aperto e messo in ascolto il socket, tutto quello
+che ci servirà sarà chiamare \func{select} per rilevare la presenza di nuove
+connessioni o di dati in arrivo, e processarli immediatamente. Per
+implementare lo schema mostrato in \figref{fig:TCP_echo_multiplex}, il
+programma usa una tabella dei socket connessi mantenuta nel vettore
+\var{fd\_open} dimensionato al valore di \macro{FD\_SETSIZE}, ed una variabile
+\var{max\_fd} per registrare il valore più alto dei file descriptor aperti.
+
+Prima di entrare nel ciclo principale (\texttt{\small 6--56}) la nostra
+tabella viene inizializzata (\texttt{\small 2}) a zero (valore che
+utilizzeremo come indicazione del fatto che il relativo file descriptor non è
+aperto), mentre il valore massimo (\texttt{\small 3}) per i file descriptor
+aperti viene impostato a quello del socket in ascolto,\footnote{in quanto esso
+  è l'unico file aperto, oltre i tre standard, e pertanto avrà il valore più
+  alto.} che verrà anche (\texttt{\small 4}) inserito nella tabella.
+
+La prima sezione (\texttt{\small 7--10}) del ciclo principale esegue la
+costruzione del \textit{file descriptor set} \var{fset} in base ai socket
+connessi in un certo momento; all'inizio ci sarà soltanto il socket in
+ascolto, ma nel prosieguo delle operazioni, verranno utilizzati anche tutti i
+socket connessi registrati nella tabella \var{fd\_open}.  Dato che la chiamata
+di \func{select} modifica il valore del \textit{file descriptor set}, è
+necessario ripetere (\texttt{\small 7}) ogni volta il suo azzeramento, per poi
+procedere con il ciclo (\texttt{\small 8--10}) in cui si impostano i socket
+trovati attivi.
+
+Per far questo si usa la caratteristica dei file descriptor, descritta in
+\secref{sec:file_open}, per cui il kernel associa sempre ad ogni nuovo file il
+file descriptor con il valore più basso disponibile. Questo fa sì che si possa
+eseguire il ciclo (\texttt{\small 8}) a partire da un valore minimo, che sarà
+sempre quello del socket in ascolto, mantenuto in \var{list\_fd}, fino al
+valore massimo di \var{max\_fd} che dovremo aver cura di tenere aggiornato.
+Dopo di che basterà controllare (\texttt{\small 9}) nella nostra tabella se il
+file descriptor è in uso o meno,\footnote{si tenga presente che benché il
+  kernel assegni sempre il primo valore libero, dato che nelle operazioni i
+  socket saranno aperti e chiusi in corrispondenza della creazione e
+  conclusione delle connessioni, si potranno sempre avere dei \textsl{buchi}
+  nella nostra tabella.} e impostare \var{fset} di conseguenza.
+
+Una volta inizializzato con i socket aperti il nostro \textit{file descriptor
+  set} potremo chiamare \func{select} per fargli osservare lo stato degli
+stessi (in lettura, presumendo che la scrittura sia sempre consentita). Come
+per il precedente esempio di \secref{sec:TCP_child_hand}, essendo questa
+l'unica funzione che può bloccarsi, ed essere interrotta da un segnale, la
+eseguiremo (\texttt{\small 11--12}) all'interno di un ciclo di \code{while}
+che la ripete indefinitamente qualora esca con un errore di \errcode{EINTR}.
+Nel caso invece di un errore normale si provvede (\texttt{\small 13--16}) ad
+uscire stampando un messaggio di errore.
+
+Se invece la funzione ritorna normalmente avremo in \var{n} il numero di
+socket da controllare. Nello specifico si danno due possibili casi diversi per
+cui \func{select} può essere ritornata: o si è ricevuta una nuova connessione
+ed è pronto il socket in ascolto, sul quale si può eseguire \func{accept} o
+c'è attività su uno dei socket connessi, sui quali si può eseguire
+\func{read}.
+
+Il primo caso viene trattato immediatamente (\texttt{\small 17--26}): si
+controlla (\texttt{\small 17}) che il socket in ascolto sia fra quelli attivi,
+nel qual caso anzitutto (\texttt{\small 18}) se ne decrementa il numero in
+\var{n}; poi, inizializzata (\texttt{\small 19}) la lunghezza della struttura
+degli indirizzi, si esegue \func{accept} per ottenere il nuovo socket connesso
+controllando che non ci siano errori (\texttt{\small 20--23}). In questo caso
+non c'è più la necessità di controllare per interruzioni dovute a segnali, in
+quanto siamo sicuri che \func{accept} non si bloccherà. Per completare la
+trattazione occorre a questo punto aggiungere (\texttt{\small 24}) il nuovo
+file descriptor alla tabella di quelli connessi, ed inoltre, se è il caso,
+aggiornare (\texttt{\small 25}) il valore massimo in \var{max\_fd}.
+
+Una volta controllato l'arrivo di nuove connessioni si passa a verificare se
+vi sono dati sui socket connessi, per questo si ripete un ciclo
+(\texttt{\small 29--55}) fintanto che il numero di socket attivi \var{n} resta
+diverso da zero; in questo modo se l'unico socket con attività era quello
+connesso, avendo opportunamente decrementato il contatore, il ciclo verrà
+saltato, e si ritornerà immediatamente (ripetuta l'inizializzazione del file
+descriptor set con i nuovi valori nella tabella) alla chiamata di
+\func{accept}. Se il socket attivo non è quello in ascolto, o ce ne sono
+comunque anche altri, il valore di \var{n} non sarà nullo ed il controllo sarà
+eseguito. Prima di entrare nel ciclo comunque si inizializza (\texttt{\small
+  28}) il valore della variabile \var{i} che useremo come indice nella tabella
+\var{fd\_open} al valore minimo, corrispondente al file descriptor del socket
+in ascolto.
+
+Il primo passo (\texttt{\small 30}) nella verifica è incrementare il valore
+dell'indice \var{i} per posizionarsi sul primo valore possibile per un file
+descriptor associato ad un eventuale socket connesso, dopo di che si controlla
+(\texttt{\small 31}) se questo è nella tabella dei socket connessi, chiedendo
+la ripetizione del ciclo in caso contrario. Altrimenti si passa a verificare
+(\texttt{\small 32}) se il file descriptor corrisponde ad uno di quelli
+attivi, e nel caso si esegue (\texttt{\small 33}) una lettura, uscendo con un
+messaggio in caso di errore (\texttt{\small 34--38}).
+
+Se (\texttt{\small 39}) il numero di byte letti \var{nread} è nullo si è in
+presenza del caso di un \textit{end-of-file}, indice che una connessione che
+si è chiusa, che deve essere trattato (\texttt{\small 39--48}) opportunamente.
+Il primo passo è chiudere (\texttt{\small 40}) anche il proprio capo del
+socket e rimuovere (\texttt{\small 41}) il file descriptor dalla tabella di
+quelli aperti, inoltre occorre verificare (\texttt{\small 42}) se il file
+descriptor chiuso è quello con il valore più alto, nel qual caso occorre
+trovare (\texttt{\small 42--46}) il nuovo massimo, altrimenti (\texttt{\small
+  47}) si può ripetere il ciclo da capo per esaminare (se ne restano)
+ulteriori file descriptor attivi.
+
+Se però è stato chiuso il file descriptor più alto, dato che la scansione dei
+file descriptor attivi viene fatta a partire dal valore più basso, questo
+significa che siamo anche arrivati alla fine della scansione, per questo
+possiamo utilizzare direttamente il valore dell'indice \var{i} con un ciclo
+all'indietro (\texttt{\small 43}) che trova il primo valore per cui la tabella
+presenta un file descriptor aperto, e lo imposta (\texttt{\small 44}) come
+nuovo massimo, per poi tornare (\texttt{\small 44}) al ciclo principale con un
+\code{break}, e rieseguire \func{select}.
+
+Se infine si sono letti dei dati (ultimo caso rimasto) si potrà invocare
+(\texttt{\small 49}) \func{FullWrite} per riscriverli indietro sul socket in
+questione, avendo cura di uscire con un messaggio in caso di errore
+(\texttt{\small 50--53}).