*
* 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 $
*
****************************************************************/
/*
#include <string.h> /* error strings */
#include <stdlib.h>
+#include "macros.h"
#include "Gapil.h"
#define BACKLOG 10
*/
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];
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 */
- 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);
}
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 */
- 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)
- && (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);
- }
- 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);
}
- 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) {
- 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 (n != 0) {
- PrintErr("Errore nella gestione dei file descriptor");
- }
}
/* normal exit, never reached */
exit(0);
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
-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
-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
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
-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
\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}).