[Avanti]  [Indietro]  [Su]  

15.3.3 Un server daytime iterativo

Dopo aver illustrato il client daremo anche un esempio di un server elementare, che sia anche in grado di rispondere al precedente client. Come primo esempio realizzeremo un server iterativo, in grado di fornire una sola risposta alla volta. Il codice del programma è nuovamente mostrato in fig. 15.9, il sorgente completo (TCP_iter_daytimed.c) è allegato insieme agli altri file degli esempi.


01: #include <sys/types.h>   /* predefined types */ 
02: #include <unistd.h>      /* include unix standard library */ 
03: #include <arpa/inet.h>   /* IP addresses conversion utilities */ 
04: #include <sys/socket.h>  /* socket library */ 
05: #include <stdio.h>       /* include standard I/O library */ 
06: #include <time.h> 
07: #define MAXLINE 80 
08: #define BACKLOG 10 
09: int main(int argc, char *argv[]) 
10: { 
11: /*  
12:  * Variables definition   
13:  */ 
14:     int list_fd, conn_fd; 
15:     int i; 
16:     struct sockaddr_in serv_add; 
17:     char buffer[MAXLINE]; 
18:     time_t timeval; 
19:     ... 
20:     /* create socket */ 
21:     if ( (list_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 
22:         perror("Socket creation error"); 
23:         exit(-1); 
24:     } 
25:     /* initialize address */ 
26:     memset((void *)&serv_add, 0, sizeof(serv_add)); /* clear server address */ 
27:     serv_add.sin_family = AF_INET;                  /* address type is INET */ 
28:     serv_add.sin_port = htons(13);                  /* daytime port is 13 */ 
29:     serv_add.sin_addr.s_addr = htonl(INADDR_ANY);   /* connect from anywhere */ 
30:     /* bind socket */ 
31:     if (bind(list_fd, (struct sockaddr *)&serv_add, sizeof(serv_add)) < 0) { 
32:         perror("bind error"); 
33:         exit(-1); 
34:     } 
35:     /* listen on socket */ 
36:     if (listen(list_fd, BACKLOG) < 0 ) { 
37:         perror("listen error"); 
38:         exit(-1); 
39:     } 
40:     /* write daytime to client */ 
41:     while (1) { 
42:         if ( (conn_fd = accept(list_fd, (struct sockaddr *) NULL, NULL)) <0 ) { 
43:             perror("accept error"); 
44:             exit(-1); 
45:         } 
46:         timeval = time(NULL); 
47:         snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&timeval)); 
48:         if ( (write(conn_fd, buffer, strlen(buffer))) < 0 ) { 
49:             perror("write error"); 
50:             exit(-1); 
51:         } 
52:         close(conn_fd); 
53:     } 
54:     /* normal exit */ 
55:     exit(0); 
56: } 
Figura 15.9: Esempio di codice di un semplice server per il servizio daytime.

Come per il client si includono (1–9) gli header necessari a cui è aggiunto quello per trattare i tempi, e si definiscono (14–18) alcune costanti e le variabili necessarie in seguito. Come nel caso precedente si sono omesse le parti relative al trattamento delle opzioni da riga di comando.

La creazione del socket (20–24) è analoga al caso precedente, come pure l'inizializzazione (25–29) della struttura sockaddr_in. Anche in questo caso (28) si usa la porta standard del servizio daytime, ma come indirizzo IP si usa (27) il valore predefinito INET_ANY, che corrisponde all'indirizzo generico.

Si effettua poi (30–34) la chiamata alla funzione bind che permette di associare la precedente struttura al socket, in modo che quest'ultimo possa essere usato per accettare connessioni su una qualunque delle interfacce di rete locali. In caso di errore si stampa (31) un messaggio, e si termina (32) immediatamente il programma.

Il passo successivo (35–39) è quello di mettere “in ascolto” il socket; questo viene fatto (36) con la funzione listen che dice al kernel di accettare connessioni per il socket che abbiamo creato; la funzione indica inoltre, con il secondo parametro, il numero massimo di connessioni che il kernel accetterà di mettere in coda per il suddetto socket. Di nuovo in caso di errore si stampa (37) un messaggio, e si esce (38) immediatamente.

La chiamata a listen completa la preparazione del socket per l'ascolto (che viene chiamato anche listening descriptor) a questo punto si può procedere con il ciclo principale (40–53) che viene eseguito indefinitamente. Il primo passo (42) è porsi in attesa di connessioni con la chiamata alla funzione accept, come in precedenza in caso di errore si stampa (43) un messaggio, e si esce (44).

Il processo resterà in stato di sleep fin quando non arriva e viene accettata una connessione da un client; quando questo avviene accept ritorna, restituendo un secondo descrittore, che viene chiamato connected descriptor, e che è quello che verrà usato dalla successiva chiamata alla write per scrivere la risposta al client.

Il ciclo quindi proseguirà determinando (46) il tempo corrente con una chiamata a time, con il quale si potrà opportunamente costruire (47) la stringa con la data da trasmettere (48) con la chiamata a write. Completata la trasmissione il nuovo socket viene chiuso (52). A questo punto il ciclo si chiude ricominciando da capo in modo da poter ripetere l'invio della data in risposta ad una successiva connessione.

È importante notare che questo server è estremamente elementare, infatti, a parte il fatto di poter essere usato solo con indirizzi IPv4, esso è in grado di rispondere ad un solo un client alla volta: è cioè, come dicevamo, un server iterativo. Inoltre è scritto per essere lanciato da linea di comando, se lo si volesse utilizzare come demone occorrerebbero le opportune modifiche18 per tener conto di quanto illustrato in sez. 10.1.5. Si noti anche che non si è inserita nessuna forma di gestione della terminazione del processo, dato che tutti i file descriptor vengono chiusi automaticamente alla sua uscita, e che, non generando figli, non è necessario preoccuparsi di gestire la loro terminazione.


[Avanti]  [Indietro]  [Su]  
© 2000-2003 Simone Piccardi
Pubblicazione web curata da Mirko Maischberger