Next Previous Contents

1. Parte 1 Descrizione socket di Berkeley

In questa prima parte si farà riferimento alle socket di Berkeley, costruendo degli esempi di programmi commentati e dando spiegazione sulle strutture e sulle chiamate che vengono messe a disposizione.

1.1 Chiamate principali delle socket di Berkeley

Per stabilire una connessione tra due processi è necessario specificare una quintupla di proprietà che permetta di identificarla univocamente:

{ Protocollo, Inidirizzo-Locale, Processo-Locale, Indirizzo-Remoto, Processo-Remoto }.

Vedremo ora in dettaglio le diverse chiamate di sistema che ci permettono di specificare queste proprieta'.



SOCKET

int socket( int famiglia, int tipo, int protocollo ) ;

La chiamata socket specifica il primo elemento della quintupla: il protocollo. La chiamata socket può ritornare:

Analiziamo i vari parametri della chiamata:

famiglia:

per svolgere I/O di rete, un sistema deve innanzitutto effetuare la chiamata di sistema socket specificando il tipo di protocollo desiderato. Le famiglie di protocolli a disposizione sono le seguenti:

Il prefisso AF sta per "address family" (famiglia di indirizzi). Esiste poi un altro insieme di termini, con prefisso PF che sta per "protocol family" (famiglia di protocolli: PF_UNIX, PF_INET, PF_NS, PF_IMPLINK ). Usare AF o PF è equivalente. IMP è l'acronimo di Interface Message Processor (elaboratore di messaggi di interfaccia); il sistema BSD fornisce una rudimentale interfaccia di socket all'IMP, che è la funzione svolta dalla famiglia di indirizzi AF_IMPLINK.

tipo: il tipo di socket può essere di diversi tipi:

Le differenze tra l'uno o l'altro fanno parte della teoria delle reti e non saranno qui approfondite.

protocollo:

specifica il protocollo utilizzato dal socket, le principali costanti per i protocolli sono:

Le costanti IPPROTO_XXX sono definite nel file <netinet/in.h>, mentre le costanti NSPROTO_XXX sono definite nel file <nets/ns.h>.



BIND

int bind( int sockfd, struct sockaddr *mioindir, int lunghindr) ;

La chiamata di sistema bind definisce gli elementi inidirizzo-locale e processo-locale della quintupla che costituisce l'associazione. La chiamata bind può essere utilizzata in 3 modi diversi:

  1. I server registrano il loro indirizzo, ben noto, nel sistema. In pratica dicono al sistema: "Questo è il mio indirizzo e qualsiasi messaggio ricevuto adesso indirizzato deve essere consegnato a me!". Questa procedura deve essere fatta prima di accettare richieste da un client sia dai server orientati alla connessione sia da quelli che non lo sono.
  2. Un client può registrare un indirizzo specifico per se stesso, in modo che il server risponda alle richieste su quel indirizzo.
  3. Un client senza connessione deve accertarsi che il sistema assegni un certo indirizzo unico, affinchè l'altra estremità abbia un indirizzo di ritorno valido su cui inviare le risposte.

Analiziamone i parametri:

sockfd: descrittore di socket precedentemente creato con la chiamata di sistema socket.

mioindr: è un puntatore all'indirizzo specifico della struttura del protocollo.

lunghdir: dimensione della struttura del protocollo.



LISTEN

int listen( int sockfd, int backlog ) ;

Questa chiamata di sistema è usata da un server orientato alla connessione per indicare che è disposto a ricevere le connessioni. Solitamente è eseguita dopo le chiamate di sistema socket e bind ed immediatamente prima della chiamata accept. I parametri sono:

sockfd: descrittore di socket precedentemente creato con la chiamata di sistema socket.

backlog: specifica il numero di richieste di connessione che possono essere accodate dal sistema mentre è in attesa che il server esegua la chiamata accept.



ACCEPT

int accept( int sockfd, struct sockaddr *pari, int lunghindir ) ;

Dopo che un server orientato alla connessione ha eseguito la chiamata di sistema listen, si pone in attesa di una eventuale connessione da parte di un processoclient. La chiamata accept prende in esame la richiesta di connessione che si ha in testa alla coda specificata dalla listen e crea un altro socket con le stesse propietà di sockfd. Se non ci sono richieste di connessione in sospeso, questachiamata blocca il chiamante finche non ne arriva una.

sockfd: Descrittore di socket fd.

lunghindr: Contiene la lunghezza della struttura dell'indirizzo del processo connesso (client).Il lunghindir viene prima posto uguale alla dimensione della struttura e successivamente prende il valore della dimensione della struttura effettivamente occupata dal client connesso.

pari: struttura di tipo sockaddr, che contiene l'indirizzo del client appena connesso (client).

Questa chiamata restituisce fino a tre valori:

  1. Intero che indica un errore.
  2. Intero che indica il nuovo socket descriptor.
  3. l'indirizzo del processo pari e la dimensione di questo indirizzo.



CONNECT

int connect( int sockfd, struct sockaddr *indirserver, int lunghindr ) ;

Mediante questa chiamata, un processo client connette un descrittore di socket facendo seguito ad una chiamata di sistema socket, al fine di stabilire una connessione con un server.Per la maggior parte dei protocolli la chiamata di sistema connect risulta l'effettiva attivazione di una connessione tra sistema locale e sistema remoto, la connessione causa l'assegnazione dei quattro elementi della 5-tupla dell'associazione: indirizzo-locale, processo-locale, indirizzo-remoto, processo-remoto. Nel caso che la chiamata venga usata con un protocollo non orientato alla connessione(UDP), la chiamata connect permette di memorizzare l'indirserver che il processo scriverà nel descrittore sockfd, in questo caso la chiamata fa immediatamente ritorno e non c'è un effettivo scambio di messaggi fra il sistema locale e il sistema remoto.

sockfd: Descrittore di socket.

indiserver: Questa struttura di tipo sockaddr contiene l'indirizzo del server al quale il client vuole connettersi.

lunghindr: Questa variabile contiene l'effettiva grandezza della struttura sockaddr che contiene l'indirizzo del server.



SEND, SENDTO, RECV, RECVFROM

int send( int sockfd, char *buff, int nbytes, int flags ) ;

int sendto( int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to,int lunghindir) ;

int recv( int sockfd, char *buff, int nbytes, int flags ) ;

int recvfrom(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int lunghindir) ;

sockfd: Rappresenta il descrittore di socket.

buff: Questo buffer serve per prelevare o inserire i dati(struttura d'appoggio).

flags: a variabile flags può assumere i seguenti significati:

Tutte e quattro le chiamate restituiscono come valore della funzione la lunghezza dei dati che sono stati scritti o letti.Nell'uso tipico di recvfrom con un protocollo senza connessione, il valore di ritorno è la lunghezza del datagramma che è stato ricevuto.



SELECT

int select( int maxfdpl, fd_set *readfds, fd_set *writefds, fd_set *expectfds, struct timeval *timeout ) ;

La select è una delle più importanti chiamate del sistema dal lato server, infatti è usata per simulare server del tipo: concorrente singolo-processo (altri tipi di server sono: server iterativo, e server concorrente multi-processo).La prima volta che un client si collega al server viene settata in una maschera di bit il socketdescriptor associato a quella connessione, successivamente quando arriva una richiesta sul socketdescriptor di lettura del server si controllerà la maschera contenente tutti sockdescriptor delle connessioni per stabilire e servire chi ha fatto la richiesta. La maschera può essere riferita ad un array che come indice usa il socketdescriptor della connessione, e come contenuto del vettore un valore bit, 1 se il client associato a quel socketdescriptor ha fatto richiesta al server, 0 se il client non sta facendo richieste al server. Le macro per manipolare la maschera di bit sono:

Sostanzialmente ci sono 3 modi in cui specificare i descrittori di file da esaminare :
  1. Il primo modo è quello di rendere bloccante la select mettendo a NULL il puntatore alla struttura Timeval.
  2. Il secondo modo è di mettere il valore del timer della struttura timeval uguale a 0,si avrà polling (interrogazioni continue) sulla maschera dei bit, senza bloccaggio.
  3. Il terzo modo è quello di mettere il timer della struttura a un certo valore,in questo modo la select interrogherà la maschera di bit ad ogni quantità di tempo(quella settata nella struttura).

Se si è resa bloccante la chiamata della select( puntatore della struttura timeval a NULL), il risveglio dipende dai seguenti eventi:

  1. uno dei socket descriptor di readfds è pronto per la lettura.
  2. uno dei socket descriptor di writefd è pronto per la scrittura.
  3. uno dei socket descriptor di exceptfds è in una eccezione pendente.

maxfdpl: numero massimo di descrittori esaminati.

readfds: maschera di bit riguardante la lettura, contenente i socket descriptor delle connessioni dei client.

writefds: maschera di bit riguardante la scrittura, contenente i socket descriptor delle connessioni dei client.

readfds: maschera di bit riguardante la lettura, contenente i socket descriptor delle connessioni dei client.

exceptfds: maschera di bit riguardante le eccezioni che si possono verificare, sulle varie connessioni dei vari client.

struttura timeval è:


struct timeval{
 long tv_sec; / secondi /
 long tv_usec;/ microsecondi /
 } 


ROUTINE IMPORTANTI

  1. Routine di ordinamento dei byte

    Le seguenti 4 funzioni gestiscono le potenziali differenze di ordinamento dei byte tra diverse architteture di computer e di diversi protocolli di rete.Anche se i problemi che sorgono con i sistemi che non impiegano byte di 8 bit stanno lentamente scomparendo , i problemi relativi all'ordinamento dei byte sono ancora presenti : Si consideri un intero di 16 bit ,2 byte ci sono 2 modi per memorizzare questo valore little endian il cui byte meno significativo è all'indirizzo iniziale e big endian in cui il byte piu significativo è all'indirizzo iniziale.

    u_long htonl( u_long hostlungo ) ;
    u_short htons( u_short, hostcorto ) ;
    u_long ntohl( u_long retelungo ) ;
    u_short ntohs( u_short retecorto ) ;

    legenda:

    htonl : converte da host a rete , long integer.
    htons : converte da host a rete, long integer.
    ntohl : converte da rete a host, long integer.
    ntohs : converte da rete a host, long integer.

  2. Routine di operazioni di byte:

    Il 4.3BSD definisce le seguenti tre routine che operano su stringhe di byte definite dall'utente. Tali routine sono "definite dall'utente" nel senso che esse non sono stringhe di caratteri standard del C (che sono sempre terminate con un byte nullo). Le stringhe di byte definite dall'utente possono avere byte nulli al loro interno e cio non indica la fine della stringa, bensi è necessario specificare la lunghezza di ciascuna stringa come argomento della funzione.

    bcopy( char *prov, char *dest, int nbytes) ;

    bzero( char *dest, int nbytes) ;

    int bcmp( char *ptr1, char *ptr2, int nbytes ) ;

    La prima funzione trasferisce il numero di byte specificato dalla sorgente alla destinazione. La seconda funzione scrive il numero di byte nulli specificati nella destinazione. La terza funzione confronta due stringhe di byte arbitrari.

  3. Routine di conversione indirizzi:

    Le seguenti funzioni operano la conversione tra il formato con un punto decimale ed una struttura in_addr:

    unsigned long inet_addr( char *) ;
    char *inet_ntoa( struct in_addr inaddr) ;

    La prima converte una stringa di caratteri da una notazione con il punto decimale ad un indirizzo di Internet di 32 bit. La seconda effettua la conversione opposta.

1.2 Client-Server e strutture delle socket BSD

STRUTTURE PRINCIPALI E VARI TIPI DI SERVER

Dopo aver introdotto le principali system call nel punto precedente, analizzeremo ora le strutture che sono indispensabili nella progettazione di un sistema in rete con le socket BSD e che permettono di definire gli indirizzi utili alla comunicazione in rete. Per le famiglie di indirizzi internet, le strutture sono definite nell'header <netinet/in.h >:


struct sockaddr{

        short sa_family ;       /* Famiglia d'indirizzo: valore AF_xxx */
        short sa_data[14] ;     /* fino a 14 byte d'indirizzo specifico del protocollo */

}

struct in_addr {

        u_long s_addr;      /* IDrete/IDhost 32 bit */
                            /* ordinato per byte di rete */
}

struct sockaddr_in{

        short sin_family ;              /* AF_INET */
        u_short  sin_port ;             /* numero di porta di 16 bit */
        struct in_addr sin_addr ;       /* struttura definita precedentemente */
        char    sin_zero[8] ;           /* non usato */ 

}


La prima struttura mediante casting, viene collegata alle altre due strutture rappresentanti indirizzi internet.



Questa struttura verrà successivamente passata alla funzione BIND che associerà la socket aperta a un numero di porta e a un indirizzo. Dopo aver descritto queste strutture che definiscono gli indirizzi di comunicazione tra diversi Host, descriviamo adesso le istruzioni di cooperazione tra client e server per avere una comunicazione tra due host con un collegamento orientato alla connessione:



             SERVER                             CLIENT
        
         ---------------                    ---------------                  
        |  sd = socket  |                  |  sd = socket  | 
        |       |       |                  |      |        |
        |       V       |                  |      |        |
        |     Bind      |                  |      |        |
        |       |       |                  |      |        |
        |       V       |                  |      |        |
        |     listen    |                  |      |        |
        |       |       |                  |      |        |
        |       V       |   connessione    |      V        |
        |     accept <------------------------> connect    |
        |       |       |                  |      |        |
        |       V       |  dati richiesta         V        |
        |   -->read  <------------------------  write<--   |
        |  |    |       |                  |      |     |  |
        |  |    V       |  dati risposta   |      V     |  |
        |   ---write  ------------------------>  read---   |
        |       |       |                  |      |        |
        |       V       |                  |      V        |
        |     close     |                  |    close      |
        |               |                  |               |
         ---------------                    ---------------

Vediamo adesso il codice del server iterativo per una comunicazione con connessione:

int sockfd, newsockfd;
...
if ( (sockfd = socket(...) ) < 0)
        err_sys("socket error");

if ( (bind(sockfd, my_addr, my_ll) < 0)
        err_sys("bind error");

if ( (listen(sockfd, 5) < 0)
        err_sys("listen error");

        for( ; ; ) {
                newsockfd=accept(sockfd,cli_addr,cli_ll);
                if (newsockfd < 0)
                        err_sys("accept error");
                /* process request on newsockfd */
                read(newsockfd, ...);
                write(newsockfd, ...);
        close (newsockfd);
}

In questo tipo di connessione, il server è di tipo iterativo cioè è lui stesso che si occupa della comunicazione con il client, con questo metodo il server riesce solo a gestire un client alla volta; vediamo adesso il codice di un server detto concorrente che permette di poter gestire più richieste attraverso la system call fork() lasciando cosi la comunicazione al figlio; il server ritorna dopo essersi sdoppiato ad ascoltare sulla porta di ricezione:


int sockfd, newsockfd;
...

if ( (sockfd = socket(...) ) < 0)
                err_sys("socket error");

if ( (bind(sockfd, myaddr, myll) < 0)
                err_sys("bind error");

if ( (listen(sockfd, 5) < 0)
        err_sys("listen error");

        for( ; ; ) {
                newsockfd=accept(sockfd,cliaddr,clill);
                if (newsockfd < 0)
                        err_sys("accept error");
                if (fork() == 0) { /* child */
                close(sockfd);
                /* process request on newsockfd */
                read(newsockfd, ...);
                write(newsockfd, ...);
                exit (0);
        }
        close (newsockfd); /* parent */
}

Il server di tipo concorrente che abbiamo descritto sopra ha però un difetto che è quello di occupare troppe risorse di sistema; abbiamo infatti un processo per ogni richiesta, in questo modo le prestazioni della macchina vengono compromesse;La soluzione è l'utilizzo di un server concorrente che usa la chiamata select.Il codice del server con la select è questo:


int sockfd, newsockfd;
int nfds;  

// STRUTTURA PER INDIRIZZI INTERNET
fd_set rfds ; 
fd_set afds ;
         
...
if ( (sockfd = socket(...) ) < 0)
        err_sys("socket error");

if ( (bind(sockfd, my_addr, my_ll) < 0)
        err_sys("bind error");

if ( (listen(sockfd, 5) < 0)
        err_sys("listen error");

        nfds = FOPEN_MAX ;//NUMERO MASSIMO DI DESCRITTORI CONSENTITI
        // INIZIALIZZA LA MSCHERA DEI DESCRITTORI PER LA SELECT
        FD_ZERO( &afds ) ;
         
         //SETTA NELLA MASCHERA IL DESCRITTORE DELLA SOCKET PASSIVA
         FD_SET( sockfd, &afds) ;
          
         
         // CICLO PER LA GESTIONI DELLE CONNESSIONI
         while( 1 ){
         
                 // COPIA DELLA STRUTTURA DALLA SORGENTE ALLA DESTINAZIONE
                 bcopy( (char*) &afds, (char*) &rfds, sizeof(rfds) ) ;
                 
                 if(select( nfds, &rfds, (fd_set *) 0, (fd_set *) 0, 
                    (struct timeval*) 0) < 0)
                 {
                    perror("select") ;
                    exit( 0 );
                 }//fine if
                    
                 // CONTROLLO DI NUOVE CONNESSIONI
                 
                 //GESTIONE DELL'INPUT SULLA SOCKET PASSIVA    
                 if(FD_ISSET(sockfd, &rfds)){
                           
                           clilen = sizeof( cli_addr ) ;
                           newsockfd = accept(sockfd,(struct sockaddr *) &cli_addr, &clilen ) ;
                           
                         if (newsockfd < 0 )
                         {
                                 perror( "accept") ;
                         }
                         else                        
                            FD_SET( newsockfd, &afds ) ;
                 }//fine if

                 
                 // GESTIONE DEI CLIENTI CONNESSI

                 for (fd=0; fd<FOPEN_MAX; fd++)
                 {
                    if(fd != sockfd && FD_ISSET(fd, &rfds))
                       if( function_communication()>0)
                       {
                          FD_CLR(fd, &afds) ;
                          close(fd);
                          printf("Chiusa connessione con partecipante\n");
                       }
                 }//fine for
         }//fine while

Per maggiori informazione sul funzionamento della select consultare il punto precedente Chiamate principali delle socket di Berkeley.Vediamo adesso il codice del client per una comunicazione con connessione:


int sockfd;
...
        if ( (sockfd=socket(...))<0)
                err_sys("socket error");

        if ( (connect(sockfd,servaddr,servll)>0)
                err_sys("connect error");

        /* process request to server */
        write(sockfd, ...);
        read(sockfd, ...);
        close(sockfd);
}

Passiamo adesso allo schema di una comunicazione senza connessione che rappresenta un tipo di server chiamato connection-less:



             SERVER                             CLIENT
        
         ---------------                    ---------------                  
        |  sd = socket  |                  |  sd = socket   | 
        |       |       |                  |      |         |
        |       V       |                  |      V         |
        |     Bind      |                  |    Bind        |
        |       |       |                  |      |         |
        |       V       | dati richiesta   |      V         |
        | --> recvfrom <----------------------- sendto <--  |
        | |     |       |                  |      |       | |
        | |     V       | dati risposta    |      V       | |
        |  -- sendto ------------------------> recvfrom --  |
        |       |       |                  |      |         |
        |       |       |                  |      |         |
        |       V       |                  |      V         |
        |     close     |                  |    close       |
        |               |                  |                |
         ---------------                    ----------------

Questi schemi riportati sono schemi standard per la comunicazione in rete, tutte le applicazioni di tipo network possono essere riportate ad un modello client-server che si divide come già sopra citato in due tipo di comunicazione uno orientato alla connessione e uno non orientato alla connessione.

1.3 Progetto di una conferenza distribuita con socket BSD

TESTO DEL PROGETTO DELLA CONFERENZA DISTRIBUITA

(Autori: Lanzi Andrea: shadow.net@tiscalinet.it, Giampaolo Fresi Roglia: gian_fresi@iol.it)


Lo scopo del progetto è quello di costruire un client-server per il distribuited conferencing in ambiente UNIX/internet.Il sistema Permette a n (n>=3) utenti di partecipare ad una sessione di distribuited conferencing.Il sistema è composto da n processi partecipanti potenzialmente attivi su diversi elaboratori in internet e da un processo coordinatore, attivo su un elaboratore ad un indirizzo (host e porta) noto. Il coordinatore conosce in ogni momento le identità dei partecipanti e degli iscritti a parlare; esso assegna il diritto di parola agli iscritti in ordine FIFO. Un partecipante che assume il ruolo di oratore invia il testo del propio intervento agli altri partecipanti e al coordinatore, che lo memorizza in un file di log.

DESCRIZIONE DEL PROGETTO:

  1. Makefile
    File contenente le istruzioni per la compilazione del codice.
    MD5: ba752ed731756bc912f02347d96a2526

  2. client.c
    contenente il programma principale dell'esecuzione del client.
    MD5: 84a8125203b4a6a18371224ab6b0f788

  3. client_subs.c
    contenente le funzioni principali del client.
    MD5: 0308d317c5ab647c7706465727e2d9b2

  4. client_subs.h
    definizione dei prototipi delle funzioni, degli header e delle strutture principali del client.
    MD5: 0308d317c5ab647c7706465727e2d9b2

  5. server.c
    contenente il programma principale dell'esecuzione del server.
    MD5: 483b8a9a79c863d27d9a4093b92d7254

  6. server_subs.c
    contenente le funzioni principali del server.
    MD5: 483b8a9a79c863d27d9a4093b92d7254

  7. server_subs.h
    definizione dei prototipi delle funzioni, degli header e delle strutture principali del server.
    MD5: 0c2ab2b09466c61204e0492582f521e7

  8. messages.h
    contiene le definizioni dei messaggi di comunicazione tra server e client.
    MD5: 77a53443d2c7217d6fce6a485f98e984

Dopo la compilazione deve essere eseguito prima il server con il numero di porte(Es. server.e 16000) e poi i vari client con l'indirizzo ip del server e la porta(Es. client.e 192.168.100.1 16000).

L'iscrizione dei client non viene effettuata automaticamente ma viene effettuata da menù.

Le nostre scelte progettuali di usare la bufferizzazione dell'input o meno derivano dall'ambiente di rete su cui si fa girare il programma: su una rete veloce su cui spedire un carattere per pacchetto non sia considerato uno spreco usare l'input non bufferizzato può essere una cosa accettabile, mentre se ciò dovesse rappresentare un problema la scelta più indicata sarebbe l'utilizzo dell'input bufferizzato.

La scelta di usare il vettore vector di dimensione FOPEN_MAX è stata dettata dalla necessità di individuare direttamente tramite l'fd di ogni socket aperta le informazioni sul client relativo ad essa. Tutte le operazioni effettuate sulle liste del server (inserimento e cancellazione) vengono effettuate direttamente tramite l'accesso al vettore evitando così l'aspetto negativo della gestione delle liste(gestione lineare).

Per quanto riguarda le strutture di memoria del server abbiamo utilizzato un vettore e due liste bidirezionali una per la gestione dei client iscritti a parlare e una per il mantenimento delle informazioni dei vari client iscritti alla conferenza(IP ADDRESS e numero di porta d'ascolto del client). IL vettore usato e costituito da 2 puntatori per ogni elemento, uno usato per puntare alla lista delle informazioni e l'altro usato per puntare alla lista degli iscritti a parlare. Ogni client è identificato dal numero intero del descrittore della socket aperta con il coordinatore e questo descrittore serve per indicizzare il vettore e recuperare le varie informazioni su di esso. Ogni operazione fatta sul client (cancellazione, aggiunta dalla conferenza ) viene effetuta tramite la socket descriptor.

I messaggi tra client e server sono definiti nel file messages.h e sono rappresentati da numeri interi.

gestione errori:

nel gestire le eventuali cadute dei client connessi abbiamo riscontrato che ad ogni caduta di socket il processo riceve un segnale di SIGPIPE che noi abbiamo gestito ignorandolo; abbiamo così potuto gestire le eventuali cadute come se fossero semplici disconnessioni.

Gli indirizzi IP dei client che si connettono al coordinatore, vengono ricavati automaticamente dai loro socket descriptor attraverso la funzione GETPEERNAME() le porte d'ascolto dei client vengono invece fatte spedire.

Per quanto riguarda il client ogni volta che gli viene data la parola gli vengono forniti tutti gli indirizzi e le porte d'ascolto dei vari partecipanti che vengono inseriti in una struttura locale.

Per la compilazione del progetto è stato fatto un Makefile, ci sono 2 possibilità di compilazione la prima con il comando:


            make

la seconda:


            make unbuffered 

La seconda compilazione permette durante la scrittura del testo da parte dell'oratore di avere un input non bufferizzato, e quindi l'immediata scrittura dei caratteri nella finestra del client; la prima compilazione effetua invece l'input bufferizzato.

Il progetto è stato provato tramite due macchine collegate a INTERNET con il server su una macchina e vari client aperti tra le due macchine, sono state fatte diverse prove tutte con esito positivo. E' stato inoltre provato tra due macchine che erano collegate con due schede di rete e i vari protocolli TCP/IP montati sopra, anche qui l'esito delle prove è stato positivo.


Next Previous Contents