[successivo] [precedente] [inizio] [fine] [indice generale]


Capitolo 6.   Esempi vari

Nei prossimi paragrafi vengono illustrati alcuni programmi che gestiscono dei socket di rete.

Questi esempi non hanno alcuna pretesa di completezza e non sono sicuramente ottimizzati per quanto riguarda l'interfaccia, i controlli sui possibili errori, l'utilizzo delle risorse; d'altra parte l'obiettivo non è la creazione di applicazioni di rete da utilizzare concretamente (di quelle ce ne sono già moltissime, libere, e spesso, anche ben fatte) ma solo di fornire degli spunti utili a fini didattici.

Nei programmi viene utilizzata la libreria «utilsock.h» contenente le inclusioni delle varie librerie necessarie al loro funzionamento e le definizioni delle funzioni personalizzate di lettura e scrittura sui socket di rete.

Il contenuto di «utilsock.h» è il seguente:(1)

/* File:         utilsock.h
*  Autore:       FF
*  Data:         05/02/2006
*  Descr.:       Inclusione librerie e definizione funzioni utili per i socket
*/
#include <unistd.h>            // interfaccia Unix standard
#include <errno.h>             // codici di errore
#include <sys/types.h>         // tipi predefiniti
#include <arpa/inet.h>         // per convertire ind. IP 
#include <sys/socket.h>        // socket  
#include <stdio.h>             // i/o
#include <stdlib.h>            // utilita'standard
#include <string.h>            // stringhe
#include <fcntl.h>             // file a basso livello
#include <time.h>              // tempo
// Funzioni
int leggisock(int fd, void *buf, size_t conta)
/*
* legge "conta" byte da un descrittore
* da usare al posto di read quando fd e' un socket di tipo stream
*/
{
    int mancano, letti;
    mancano=conta;
    while (mancano>0) {
        letti = read(fd, buf, mancano);
        if ( (letti<0) && (errno!=EINTR) )
            return(letti);         // errore
        else
            if (letti==0)
                break;            // EOF
        mancano = mancano - letti;
        buf = buf + letti;
    }            
    return(conta - mancano);
}    

int scrivisock(int fd, void *buf, size_t conta)
/*
* scrive "conta" byte su un descrittore
* da usare al posto di write quando fd e' un socket di tipo stream
*/
{
    int mancano, scritti;
    mancano=conta;
    while (mancano>0) {
        scritti = write(fd, buf, mancano);
        if ( (scritti<=0) && (errno!=EINTR) )
            return(scritti);         // errore
        mancano = mancano - scritti;
        buf = buf + scritti;
    }            
    return(conta - mancano);
}

6.1   Servente concorrente che riceve un file da un cliente connesso

Questo esempio consiste in un processo servente che accetta connessioni da un cliente e riceve da quest'ultimo il contenuto di un file e lo memorizza in un file di output.

Nel primo listato abbiamo il codice del servente che deve essere lanciato fornendo come parametro la porta da ascoltare; dopo aver creato il file di log il programma crea il socket, prepara l'indirizzo con le necessarie conversioni di formato e esegue la bind() e la listen().

Il cuore del programma inizia al momento della chiamata alla accept() che crea il socket connesso; qui il programma crea un figlio (con la funzione fork()) e si rimette in ascolto, mentre il figlio gestisce la connessione svolgendo le seguenti elaborazioni:

/* Programma:    servente_c
*  Autore:       FF
*  Data:         05/02/2006
*  Descr.:       Esempio di servente concorrente TCP che accetta connessione,
*                la registra su un file di log  e riceve un file dal cliente da 
*                memorizzare nella directory corrente con nome composto
*                da ip_cliente:porta_data_ora;
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#define MAX        80
#define BACKLOG 20

int main(int argc, char *argv[])
{
    int sd, accsd,fd,fdw,l,scritti,letti;
    struct sockaddr_in serv_ind, cliente;
    char buffer[MAX],datil[MAX],indcli[128];
    socklen_t lung;
    pid_t pid;
    in_port_t porta;
    char stringa[MAX], stringa2[MAX];
    time_t    now;
    struct    tm    *tnow;        // struttura definita in &lt;time.h&gt;
    char tempo[24], tempo2[24];
    if (argc != 2) {
        printf("\nErrore\n");
        printf("Attivare con ./servente_c porta(>1023)\n");
        exit(-1);
    }
    // apertura file di log con xor fra i flag O_RDWR, O_CREAT, O_APPEND
    // definiti in &lt;fcntl.h&gt;; apre in lettura-scrittura, append e lo
    // crea se serve     
    fd=open("log_servente", O_RDWR |O_CREAT | O_APPEND,0644);
    // crea socket 
    if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Errore nella creazione del Socket");
        exit(-1);
    }
    // indirizzo ip
    memset((void *)&serv_ind, 0, sizeof(serv_ind)); // pulizia ind
    serv_ind.sin_family = AF_INET;                  // ind di tipo INET 
    porta = atoi(argv[1]);
    serv_ind.sin_port = htons(porta);               // porta 
    serv_ind.sin_addr.s_addr = htonl(INADDR_ANY);   // connessioni da ogni ip
    // bind 
    if (bind(sd, (struct sockaddr *)&serv_ind, sizeof(serv_ind)) < 0) {
        perror("Errore nella bind");
        exit(-1);
    }
    // listen 
    if (listen(sd, BACKLOG) < 0 ) {
        perror("Errore nella listen");
        exit(-1);
    }
    printf ("Servente in ascolto sulla porta %d\n",porta); 
    printf ("'Ctrl+c' per chiuderlo\n\n");
    lung = sizeof(struct sockaddr_in); //lung della giusta dimensione
    // accetta connessione
    while (1) {
        if ((accsd = accept(sd, (struct sockaddr *)&cliente, &lung))<0) {
            perror("Errore nella accept");
            exit(-1);
        }
        if ((pid=fork()) <0) {
            perror("Errore nella fork");
            exit(-1);        
        }
        if (pid == 0) {      // figlio
            close(sd);
            time (&now);
            tnow = localtime(&now);
            strftime(tempo,24,"%b %d %H:%M:%S %Y",tnow);
            strftime(tempo2,24,"%Y_%b_%d_%H:%M:%S",tnow);
            inet_ntop(AF_INET, &cliente.sin_addr, indcli, sizeof(indcli));
            l=strlen(indcli);
            snprintf(stringa,55+l,"%s Richiesta da nodo %s, porta %5d\n",
                     tempo,indcli,ntohs(cliente.sin_port));
            snprintf(stringa2,30+l,"%s:%5d_%s",indcli,ntohs(cliente.sin_port),
                     tempo2);
            printf("%s",stringa);        
            write (fd,stringa,strlen(stringa));        // scrive nel file di log        
            strcpy(buffer,"Connessione accettata\n");
            if ( (scrivisock(accsd, buffer, strlen(buffer))) < 0 ) {
                perror("Errore nella write");
                exit(-1);
            }
            // apertura file di output con xor fra i flag O_RDWR, O_CREAT
            // lettura-scrittura e lo crea se serve     
            fdw=open(stringa2, O_RDWR |O_CREAT | O_APPEND,0644);
            // scrive nel file di output i dati letti dal socket
            // per leggere usa leggisock() definita in utilsock.h
            while(1) {
                if((letti=leggisock(accsd,datil,MAX))==0) 
                    break;            
                scritti=write(fdw,datil,letti);
            }    
            close(accsd);
            exit(0);
        }
        else  {              // padre         
            close(accsd);
        }        
    }
close(fd);
close(fdw);    
exit(0);
}

Nel secondo listato abbiamo il codice del cliente che deve essere lanciato fornendo nell'ordine:

Il programma verifica che il file da trasferire esista, quindi crea il socket e definisce l'indirizzo con le necessarie conversioni di formato; infine, con connect(), stabilisce la connessione dalla quale legge il messaggio di conferma inviato dal servente e nella quale scrive i dati letti dal file da trasferire.(3)

/* Programma:    cliente.c
*  Autore:       FF
*  Data:         05/02/2006
*  Descr.:       Esempio di cliente TCP che chiede una connessione
*                riceve risposta e termina
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#define MAX        80

int main(int argc, char *argv[])
{
    int sd,n,i,fd, scritti,letti;
    struct sockaddr_in serv_ind;
    in_port_t porta;
    char buffer[MAX], datil[MAX];
    // controlla argomenti
    if (argc !=4) {
        printf("\nErrore\n");
           printf("Esempio di connessione per traferire file: \n");
        printf("./client ip_servente porta (>1023) nome_file\n");
        exit(-1);
    }
    // apre file per verificarne esistenza
    if ( (fd=open(argv[3], O_RDONLY))<0) {    // O_RDONLY flag def. in <fcntl.h> 
        perror("Errore in apertura file da trasferire");
        exit(-1);
    }    
        // crea socket 
    if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Errore in creazione socket");
        exit(-1);
    }
    // indirizzo IP
    memset((void *)&serv_ind, 0, sizeof(serv_ind)); // pulizia ind
    serv_ind.sin_family = AF_INET;                  // ind di tipo INET 
    porta = atoi(argv[2]);
    serv_ind.sin_port = htons(porta);               // porta a cui collegarsi
    /* crea indirizzo usando inet_pton */
    if ( (inet_pton(AF_INET, argv[1], &serv_ind.sin_addr)) <= 0) {
        perror("Errore in creazione indirizzo");
        return -1;
    }
    // stabilisce la connessione
    if (connect(sd, (struct sockaddr *)&serv_ind, sizeof(serv_ind)) < 0) {
        perror("Errore nella connessione");
        exit(-1);
    }
    // legge dal servente
    if ( (n=read(sd, buffer, MAX))<=0) {
        perror("Errore nella read");
        exit(-1);
    }
    buffer[n]='\0';
    if (fputs(buffer, stdout) == EOF) {          
        perror("Errore nella fputs");
        exit(-1);
    }        
    // legge i dati dal file da trasferire e li scive sul socket
    // usa la funzione scrivisock() di utilsock.h        
    while(1) {
        letti=read(fd,datil,MAX);
        if(letti==0)  {
            break;        
        }    
        scritti=scrivisock(sd,datil,letti);
        if (scritti<letti) {
            printf("Errore in scrittura sul socket\n");        
        }    
    }
    printf("File %s trasferito\n",argv[3]);    
    close (sd);
    close (fd);        
    exit(0);
}

Nelle figure seguenti vediamo l'esecuzione dei due programmi: in figura 6.4 il servente, dopo che ha ricevuto alcune richieste di connessione, in figura 6.5 il cliente.

Figura 6.4

figure/progr-socket-figura-ese1_1

.

.

Figura 6.5

figure/progr-socket-figura-ese1_2

6.2   Servente iterativo che scambia messaggi con un cliente

Questo esempio consiste in un processo servente che accetta una connessione da un cliente per scambiare messaggi con quest'ultimo.

La comunicazione è half-duplex nel senso che ognuno dei due nodi invia un messaggio e si pone in attesa della risposta, ricevuta la quale può inviare un nuovo messaggio; il nodo che deve iniziare la comunicazione è il servente.

Nel primo listato c'è il servente che deve essere lanciato indicando la porta in cui si mette in ascolto.

Dopo aver eseguito le funzioni di attivazione e ascolto del socket, al verificarsi della connessione, cioè dopo la accept(), esegue un ciclo nel quale scambia messaggi con il cliente connesso finché uno dei due nodi non invia la stringa «/ciao».

La differenza importante rispetto all'esempio precedente è nel fatto che qui non viene creato alcun processo figlio ed è il processo lanciato che si occupa direttamente di gestire la connessione (servente iterativo anziché concorrente).(4)

/* Programma:     servente_i_msg
*  Autore:        FF
*  Data:          06/02/2006
*  Descr.:        Esempio di servente iterativo TCP che accetta una connessione
*                 da un cliente e scambia messaggi con esso
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#define MAX        256
#define BACKLOG 20

int main(int argc, char *argv[])
{
    int sd,accsd,l;
    struct sockaddr_in serv_ind, cliente;
    char msg[MAX],indcli[128];
    int msglun=MAX,val=1;
    socklen_t lung;
    in_port_t porta;
    printf("\033[2J\033[H");
    // controlla argomenti
    if (argc !=2) {
        printf("\nErrore\n");
           printf("Si deve digitare: \n");
        printf("./servente_i_msg porta (>1023)\n");
        printf("Premere Invio\n");
        getchar();
        exit(-1);
    }
    // crea socket 
    if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Errore nella creazione del Socket");
        exit(-1);
    }
    // rende il socket riusabile
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
    // indirizzo ip
    memset((void *)&serv_ind, 0, sizeof(serv_ind)); // pulisce ind
    serv_ind.sin_family = AF_INET;                  // ind di tipo INET 
    porta=atoi(argv[1]);
    serv_ind.sin_port = htons(porta);               // scelgo porta non priv.
    serv_ind.sin_addr.s_addr = htonl(INADDR_ANY);   // connessioni da ogni ip
    // bind socket 
    if (bind(sd, (struct sockaddr *)&serv_ind, sizeof(serv_ind)) < 0) {
        perror("Errore nella bind");
        exit(-1);
    }
    // listen 
    if (listen(sd, BACKLOG) < 0 ) {
        perror("Errore nella listen");
        exit(-1);
    }
    printf("In ascolto sulla porta %d\n",porta);
    // accetta connessione
    if ( (accsd = accept(sd, (struct sockaddr *)&cliente, &lung)) <0 ) {
        perror("Errore nella accept");
        exit(-1);
    }
    inet_ntop(AF_INET, &cliente.sin_addr, indcli, sizeof(indcli));
    printf("Cliente %s connesso\n",indcli);
    printf("Per terminare digitare il messaggio: /ciao\n");
    do {
      printf("Messaggio da inviare: ");
      for(l=0;((msg[l]=getchar())!='\n' && l<MAX-1);l++);
      msg[l]='\0';     
      scrivisock(accsd,msg,l+1);
      printf("OK messaggio inviato\n");    
      printf("In attesa di messaggio\n\n");      
      if ( (read(accsd,msg,msglun))<=0 ) {
          perror("Errore nella read");
          exit(-1);
      }       
      printf("Messaggio ricevuto: %s\n",msg);
    }
    while(strcmp(msg,"/ciao")); // usare /ciao per finire
    close(accsd);
    close(sd);
exit(0);
}

Nel prossimo listato abbiamo il cliente che deve essere lanciato fornando il nome del servente e la porta a cui collegarsi.

L'indirizzo IP da utilizzare per la connessione viene determinato in base al nome del servente con la funzione gethostbyname().

Dopo la connessione viene eseguito il ciclo di scambio dei messaggi con l'altro nodo fino alla digitazione di «/ciao».(5)

/* Programma:     cliente_msg
*  Autore:        FF
*  Data:          06/02/2006
*  Descr.:        Esempio di cliente TCP che chiede una connessione
*                 e scambia messaggi con un servente del quale 
*                 determina l'IP conoscendo il nome
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#include <netdb.h>
#define MAX        256

int main(int argc, char *argv[])
{
    int sd,l;
    struct sockaddr_in serv_ind;
    struct hostent *ipserv;    
    in_port_t porta;
    char msg[MAX];
    int msglun=MAX;
    printf("\033[2J\033[H");
    // controlla argomenti
    if (argc !=3) {
        printf("\nErrore\n");
           printf("Esempio di connessione: \n");
        printf("./client nome_servente porta (>1023)\n");
        printf("Premere Invio\n");
        getchar();
        exit(-1);
    }
    ipserv=gethostbyname(argv[1]);    // trova IP servente in base al nome
    // crea socket 
    if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Errore in creazione socket");
        exit(-1);
    }
    // indirizzo IP
    memset((void *)&serv_ind, 0, sizeof(serv_ind)); // pulizia ind
    serv_ind.sin_family = AF_INET;                  // ind di tipo INET 
    porta = atoi(argv[2]);
    serv_ind.sin_port = htons(porta);               // porta a cui collegarsi
    memcpy(&serv_ind.sin_addr.s_addr,ipserv->h_addr,ipserv->h_length);
    // copiato ip servente dalla struct ipserv a serv_ind.sin_addr
    // stabilisce la connessione
    if (connect(sd, (struct sockaddr *)&serv_ind, sizeof(serv_ind)) < 0) {
        perror("Errore nella connessione");
        exit(-1);
    }
    printf("Connessione stabilita con %s\n",inet_ntoa(serv_ind.sin_addr));
    printf("Per terminare digitare il messaggio: /ciao\n");
    printf("In attesa di messaggio\n\n");
    do {
      if ( (read(sd,msg,msglun))<=0 ) {
          perror ("Errore nella read");
          exit(-1);
      }      
      printf("Messaggio ricevuto: %s\n",msg);
      if (strcmp(msg,"/ciao")){
          printf("Messaggio da inviare: ");
           for(l=0;((msg[l]=getchar())!='\n' && l<MAX-1);l++);
           msg[l]='\0';           
           scrivisock(sd,msg,l+1);
           printf("OK messaggio inviato\n");
           printf("In attesa di messaggio\n\n");
      }
   }
   while (strcmp(msg,"/ciao"));        // msg /ciao per finire
   close(sd);
   exit(0);
}

Nelle figure 6.8 e 6.9 viene mostrato un esempio di dialogo tra il servente e il cliente.

Figura 6.8

figure/progr-socket-figura-ese2_1

.

Figura 6.9

figure/progr-socket-figura-ese2_2

6.3   Servente di chat con multiplexing

In questo esempio abbiamo un servente di chat che accetta connessioni da vari clienti, riceve i dati da uno qualsiasi di essi e li invia a tutti gli altri.

Per gestire l'input proveniente da nodi diversi viene utilizzato il multiplexing sul socket di rete in modo da poter leggere i dati da uno qualsiasi dei clienti collegati.

Ovviamente c'è anche il programma cliente da utilizzare per collegarsi al servente e «partecipare» alla chat; anche qui viene usato il multiplexing ma per uno scopo diverso e cioè per controllare contemporaneamente l'input dalla tastiera e dal socket di rete.

Nel primo listato c'è il servente che deve essere lanciato indicando la porta in cui si mette in ascolto.

Il socket viene reso riutilizzabile con la funzione setsockopt(), quindi, dopo la bind() e la listen() viene preparata la lista dei descrittori da utilizzare con la select().

A questo punto il programma entra in un ciclo infinito (che si interrompe con Ctrl+c) nel quale «ascolta» i socket attivi.

Inizialmente non c'è alcun cliente collegato; via via che si collegano viene eseguita la accept() e il descrittore del socket connesso entra a far parte della lista dei descrittori gestita dalla select(); quando invece un cliente si disconnette, il relativo descrittore viene tolto dalla lista.

Sempre grazie alla select() i dati inviati da un certo nodo vengono recepiti e inviati a tutti gli altri nodi connessi.(6)

/* Programma:     servente_chat
*  Autore:        FF
*  Data:          08/02/2006
*  Descr.:        Esempio di servente di chat che riceve connessioni 
*                 e le gestisce con la funzione select (multiplexing) invece 
*                 di creare un figlio per ogni connessione; tutto quello che
*                 inviato da un cliente viene rispedito dal servente
*                 a tutti gli altri clienti
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#include <sys/select.h>
#include <netdb.h>
#define MAX        256
#define BACKLOG 20

int main(int argc, char *argv[])
{
    fd_set pri;                     // pri lista di descrittori per select()
    fd_set set_lettura;             // temp altra lista di descrittori
    struct sockaddr_in indserv;     // indirizzo del servente
    struct sockaddr_in indcli;      // indirizzo di un cliente
    int sdgr;                       // numero descrittore piu' alto
    int sd;                         // socket
    int csd;                        // socket connesso con accept()
    char buf[MAX],buf2[MAX];        // buffer dei dati
    int bytel;
    in_port_t porta;
    int val=1;                      // per la setsockopt()
    socklen_t lind;
    struct hostent *cli;            // per trovare nome cliente dall'IP
    struct in_addr vet[MAX];        // per mem. IP cliente (max 256 clienti)        
    int i, j;
    printf("\033[2J\033[H");
    //controlli sui parametri
    if (argc!=2) {
        printf("Digitare ./servente_chat porta\n");        
        exit(-1);
    }
    porta=atoi(argv[1]);    
    FD_ZERO(&pri);                    // pulizia liste descrittori
    FD_ZERO(&set_lettura);
    // socket
    if ((sd = socket(PF_INET, SOCK_STREAM, 0))<0) {
        perror("Errore nella socket");
        exit(-1);
    }
    // rende l'indirizzo riutilizzabile
    if (setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(int))<0) {
        perror("Errore nella setsockopt");
        exit(-1);
    }
    // bind
    indserv.sin_family = AF_INET;
    indserv.sin_addr.s_addr = INADDR_ANY;
    indserv.sin_port = htons(porta);
    memset(&(indserv.sin_zero), '\0', 8);
    if (bind(sd, (struct sockaddr *)&indserv, sizeof(indserv)) == -1) {
        perror("Errore nella bind");
        exit(-1);
    }
    // listen
    if (listen(sd,BACKLOG)<0) {
        perror("Errore nella listen");
        exit(-1);
    }
    printf("servente_chat pronto; 'Ctrl+c' per chiuderlo\n\n");
    // aggiunge sd al set principale
    FD_SET(sd, &pri);
    // conserva il descrittore di file piu' grande
    sdgr = sd; 
    /*
    * cuore del programma:
    * prima della select copia la lista pri (che contiene tutti i socket 
    * aperti e quello tuttora in ascolto) in set_lettura in modo che pri 
    * non venga "sporcato" dalla select; set_lettura è usato solo nella select 
    * e nel successivo test per rilevare socket attivi; eventuali nuovi
    * socket connessi vengono aggiunti a pri cosi' come i socket chiusi
    * vengono rimossi da pri
    */
    while(1) {
        set_lettura = pri;                 // imposta set per la select()
        if (select(sdgr+1, &set_lettura, NULL, NULL, NULL)<0) {
            perror("Errore nella select");
            exit(-1);
        }
        // ispeziona le connessioni per cercare dati in arrivo
        for(i=0;i<=sdgr;i++) {
            if (FD_ISSET(i, &set_lettura)) {     // trovato un socket con dati
                if (i==sd) {
                    lind = sizeof(indcli);
                    if ((csd=accept(sd,(struct sockaddr *)&indcli,&lind))<0) { 
                        perror("Errore nella accept");
                    }
                    else {
                        FD_SET(csd, &pri);   // aggiunge socket conn al set pri
                        if (csd > sdgr) {    
                            sdgr = csd;
                        }
                        printf("servente_chat: connessione da %s sul "
                            "socket %d\n",inet_ntoa(indcli.sin_addr),csd);    
                        vet[csd]=indcli.sin_addr;         // conserva IP cliente
                    }
                }
                else {
                    // gestisce dati in arrivo da un cliente                    
                    if ((bytel=leggisock(i,buf,sizeof(buf)))<= 0) {

                        if (bytel==0) {
                            // connessione chiusa dal cliente
                            printf("servente_chat: socket %d chiuso\n", i);
                        }
                        else {
                            perror("Errore in ricezione");
                        }
                        close(i); 
                        FD_CLR(i,&pri);     // toglie il socket da pri
                    }
                    else {    
                        // invia i dati ai vari clienti
                        // prima recupera IP del mittente e ne trova il nome
                        // grazie a IP salvato in vet[i]; mette il nome
                        // in buf2 in testa ai dati da inviare
                        strcpy(buf2,"Da: ");
                        cli=gethostbyaddr((char *)&vet[i],4,AF_INET);
                        if (cli!=NULL) {
                            strcat(buf2,cli->h_name);                            
                        }
                        else {
                            strcat(buf2,inet_ntoa(vet[i]));                            
                        }    
                        strcat(buf2," ----> ");
                        strcat(buf2,buf);    
                        for(j=0;j<=sdgr;j++) {
                            if (FD_ISSET(j, &pri)) {
                                // non invia i dati al mittente
                                if (j!=sd && j!=i) {
                                    if (scrivisock(j,buf2,bytel)<0) {
                                          perror("Errore in spedizione");
                                    }
                                }
                            }             // fine if (FD_ISSET(j.....))
                        }                // fine for j<=sdgr
                    }                    // fine else: invia i dati
                }                        // fine else: gestisce dati in arrivo 
            }                            // fine if (FD_ISSET(i.....))
        }                                // fine for i<=sdgr
    }                                    // fine while(1)
    return 0;
}

Nel prossimo listato abbiamo il cliente di chat che deve essere lanciato fornando il nome del servente e la porta a cui collegarsi.

L'indirizzo IP da utilizzare per la connessione viene determinato in base al nome del servente con la funzione gethostbyname().

Come detto, anche qui viene utilizzata la funzione select() allo scopo di controllare simultaneamente la tastiera e il socket di rete, dopo la connessione.(7)

/* Programma:     cliente_chat
*  Autore:        FF
*  Data:          08/02/2006
*  Descr.:        Esempio di cliente TCP che chiede una connessione
*                 a un servente per scmbiare messaggi con altri clienti; 
*                 determina l'IP del servente dal nome; usa select
*                 per gestire il mutiplexing fra tastiera e socket di rete
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#include <netdb.h>
#include <sys/select.h>
#define MAX        256

int main(int argc, char *argv[])
{
    int sd,l;
    struct sockaddr_in serv_ind;
    struct hostent *ipserv;    
    in_port_t porta;
    char msg[MAX];
    int msglun=MAX;
    fd_set setl,setw,sete;
    printf("\033[2J\033[H");
    // controlla argomenti
    if (argc !=3) {
        printf("\nErrore\n");
           printf("Esempio di connessione: \n");
        printf("./client nome_servente porta (>1023)\n");
        printf("Premere Invio\n");
        getchar();
        exit(-1);
    }
    ipserv=gethostbyname(argv[1]);    // trova IP servente in base al nome
    // crea socket 
    if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Errore in creazione socket");
        exit(-1);
    }
    // indirizzo IP
    memset((void *)&serv_ind, 0, sizeof(serv_ind)); // pulizia ind
    serv_ind.sin_family = AF_INET;                  // ind di tipo INET 
    porta = atoi(argv[2]);
    serv_ind.sin_port = htons(porta);               // porta a cui collegarsi
    memcpy(&serv_ind.sin_addr.s_addr,ipserv->h_addr,ipserv->h_length);
    // copiato ip servente dalla struct ipserv a serv_ind.sin_addr
    // stabilisce la connessione
    if (connect(sd, (struct sockaddr *)&serv_ind, sizeof(serv_ind)) < 0) {
        perror("Errore nella connessione");
        exit(-1);
    }
    printf("Connessione stabilita con %s\n",inet_ntoa(serv_ind.sin_addr));
    printf("Per terminare digitare il messaggio: /ciao\n");    
    while (1)
    {
           FD_ZERO (&setl);      // azzera i set; solo setl viene davvero usato
           FD_ZERO (&setw);
           FD_ZERO (&sete);
           FD_SET(fileno(stdin),&setl); // aggiunge descrittore di stdin in setl
           FD_SET(sd,&setl);
           if (select(FD_SETSIZE,&setl,&setw,&sete, NULL)<0) {
              perror("Errore nella select");
              exit(1);
           }
           if (FD_ISSET(fileno(stdin),&setl)) {    // tastiera
              for(l=0;((msg[l]=getchar())!='\n' && l<MAX-1);l++);
              msg[l]='\0';
            if (strcmp("/ciao",msg)==0) {
                printf("Fine\n");
                break;
            }    
            scrivisock(sd,msg,MAX);
            printf("<---- OK\n");
        }
           if (FD_ISSET (sd,&setl)) {          
            if ( (read(sd,msg,msglun))<=0 ) {
                perror("Errore nella read");
                exit(-1);
            }    
             printf("%s\n",msg); 
        }
    } 
    close(sd);
    exit(0);
}

Nelle figure 6.12, 6.13, 6.14 e 6.15 viene mostrato un esempio di chat con il servente e tre clienti coinvolti.

Figura 6.12

figure/progr-socket-figura-ese3_1

.

Figura 6.13

figure/progr-socket-figura-ese3_2

.

Figura 6.14

figure/progr-socket-figura-ese3_3

.

Figura 6.15

figure/progr-socket-figura-ese3_4

6.4   Acquisizione dei dati delle interfacce di rete con ioctl()

In questo esempio abbiamo l'utilizzo della funzione ioctl() per acquisire informazioni sulle interfacce di rete presenti sul sistema.(8)

/* Programma:     sock_ioctl
*  Autore:        FF
*  Data:          10/02/2006
*  Descr.:        Esempio in cui si visualizzano le interfacce di rete
*                 della macchina con relativi indirizzi IP, MAC, NETMASK
*                 e BROADCAST usando la funzione ioctl()
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#include <sys/ioctl.h>
#include <net/if.h>
#define MAX        256

int main(int argc, char *argv[])
{
    int sd;                           // socket
    char buf[1024];                   // buffer dei dati
    struct ifreq *ifr = NULL;
    struct ifconf ifc;
    unsigned char *indmac;    
    int i;
    // socket
    if ((sd = socket(PF_INET,SOCK_STREAM,0))<0) {
        perror("Errore nella socket");
        exit(-1);
    }
    // Recupero delle informazioni delle interfacce
    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = buf;
    if (ioctl(sd, SIOCGIFCONF, &ifc)< 0) {
        perror("Errore nella ioctl SIOCGIFCONF");
        exit(-1);
    }
    ifr=ifc.ifc_req;
    for (i=0; i< ifc.ifc_len / sizeof(struct ifreq); i++, ifr++) 
    {
        printf("Interfaccia: %s\n", ifr->ifr_ifrn.ifrn_name);
        // IP  
        if (ioctl(sd, SIOCGIFADDR, ifr)< 0) {
            perror("Errore nella ioctl SIOCGIFADDR");
            exit(-1);
        }
        printf("IP: %s\n",inet_ntoa
                (((struct sockaddr_in *)&(ifr->ifr_addr))->sin_addr));
        // MAC
        if (ioctl(sd, SIOCGIFHWADDR, ifr)< 0) {
            perror("Errore nella ioctl SIOCGIFHWADDR");
            exit(-1);
        }
        indmac=ifr->ifr_hwaddr.sa_data; 
        // usato sa_data che è un campo della struttuta sockaddr generica                                    
        printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
                indmac[0],indmac[1],indmac[2],indmac[3],indmac[4],indmac[5]);    
        // Netmask
        if (ioctl(sd, SIOCGIFNETMASK, ifr)< 0) {
            perror("Errore nella ioctl SIOCGIFNETMASK");
            exit(-1);
        }
        printf("Netmask: %s\n",inet_ntoa
                (((struct sockaddr_in *)&(ifr->ifr_netmask))->sin_addr));
        // Broadcast 
        if (ioctl(sd, SIOCGIFBRDADDR, ifr)< 0) {
            perror("Errore nella ioctl SIOCGIFBRDADDR");
            exit(-1);
        }
        printf("Broadcast: %s\n\n",inet_ntoa
                (((struct sockaddr_in *)&(ifr->ifr_broadaddr))->sin_addr));
    }    
    close(sd);
    exit(0);       
}

Nella figura 6.17 vediamo il risultato di una esecuzione del programma su un sistema con diverse interfacce di rete.

Figura 6.17

figure/progr-socket-figura-ese4_1


1) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/utilsock.h>.

2) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/servente.c>.

3) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/cliente.c>.

4) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/servente_i_msg.c>.

5) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/cliente_msg.c>.

6) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/servente_chat.c>.

7) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/cliente_chat.c>.

8) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/progr-socket/sock_ioctl.c>.


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome esempi_vari.html

[successivo] [precedente] [inizio] [fine] [indice generale]

Valid ISO-HTML!

CSS validator!