Skip to content

9. Codice sorgente dei programmi

9.1. Il client generico TCP

Molti servizi creati agli albori di Internet funzionano secondo il modello del server di eco studiato in precedenza: gli scambi client-server avvengono tramite lo scambio di righe di testo. Scriveremo un client TCP generico che verrà avviato nel modo seguente: java cltTCPgenerique server porta

Questo client TCP si connetterà alla porta port del server serveur. Una volta fatto ciò, creerà due thread:

  1. un thread incaricato di leggere i comandi digitati sulla tastiera e di inviarli al server
  2. un thread incaricato di leggere le risposte del server e di visualizzarle sullo schermo

Perché due thread? Ogni servizio TCP-IP ha un proprio protocollo specifico e talvolta si verificano le seguenti situazioni:

  • il client deve inviare più righe di testo prima di ricevere una risposta
  • la risposta di un server può contenere più righe di testo

Pertanto, il ciclo che prevede l’invio di una singola riga al server e la ricezione di una singola riga inviata dal server non è sempre adeguato. Creeremo quindi due cicli distinti:

  • un ciclo di lettura dei comandi digitati sulla tastiera da inviare al server. L’utente segnalerà la fine dei comandi con la parola chiave fin.
  • un ciclo di ricezione e visualizzazione delle risposte del server. Si tratterà di un ciclo infinito che verrà interrotto solo dalla chiusura del flusso di rete da parte del server o dall’utente tramite la tastiera, digitando il comando fin.

Per avere questi due cicli separati, abbiamo bisogno di due thread indipendenti. Vediamo un esempio di esecuzione in cui il nostro client TCP generico si connette a un servizio SMTP (SendMail Transfer Protocol). Questo servizio è responsabile dell'inoltro della posta elettronica ai destinatari. Funziona sulla porta 25 e utilizza un protocollo di comunicazione basato sullo scambio di righe di testo.


Dos>java clientTCPgenerique istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test

ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Commentiamo questi scambi client-server:

  • il servizio SMTP invia un messaggio di benvenuto quando un client si connette ad esso:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • alcuni servizi dispongono di un comando help che fornisce indicazioni sui comandi utilizzabili con il servizio. In questo caso non è così. I comandi SMTP utilizzati nell'esempio sono i seguenti:
    • mail from: expéditeur, per indicare l’indirizzo e-mail del mittente del messaggio
    • rcpt to: destinataire, per indicare l’indirizzo e-mail del destinatario del messaggio. Se ci sono più destinatari, il comando rcpt to: va ripetuto tante volte quante necessarie per ciascuno di essi.
    • data, che segnala al server SMTP che si sta per inviare il messaggio. Come indicato nella risposta del server, il messaggio è costituito da una sequenza di righe terminata da una riga contenente il solo carattere punto. Un messaggio può avere delle intestazioni separate dal corpo del messaggio da una riga vuota. Nel nostro esempio, abbiamo inserito un oggetto con la parola chiave Subject:
  • una volta inviato il messaggio, è possibile indicare al server che si è terminato con il comando quit. Il server chiude quindi la connessione di rete. Il thread di lettura può rilevare questo evento e arrestarsi.
  • L’utente digita quindi «fin» sulla tastiera per arrestare anche il thread di lettura dei comandi digitati sulla tastiera.

Se controlliamo la posta ricevuta, troviamo quanto segue (Outlook):

Image

Si noti che il servizio SMTP non è in grado di rilevare se un mittente sia valido o meno. Pertanto, non ci si può mai fidare del campo from di un messaggio. In questo caso, il mittente machin@univ-angers.fr non esisteva.

Questo client TCP generico ci permette di individuare il protocollo di comunicazione dei servizi Internet e, partendo da lì, di creare classi specializzate per i client di tali servizi. Scopriamo il protocollo di comunicazione del servizio POP (Post Office Protocol), che consente di recuperare le proprie e-mail archiviate su un server. Funziona sulla porta 110.


Dos> java clientTCPgenerique istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<--     by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<--     Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<--     by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<--     Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2                  Tl.  : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 ,  Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

I comandi principali sono i seguenti:

  • user login, dove si inserisce il proprio nome utente sul computer che ospita le nostre e-mail
  • pass password, dove si inserisce la password associata al login precedente
  • list, per ottenere l'elenco dei messaggi sotto forma di numero e dimensione in byte
  • retr i, per leggere il messaggio n. i
  • quit, per terminare la sessione.

Scopriamo ora il protocollo di comunicazione tra un client e un server Web che solitamente opera sulla porta 80:


Dos> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise  jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Un client Web invia i propri comandi al server secondo lo schema seguente:

commande1
commande2
...
commanden
[ligne vide]

Il server Web risponde solo dopo aver ricevuto la riga vuota. Nell'esempio abbiamo utilizzato un solo comando:

GET /index.html HTTP/1.0

che richiede al server il file URL /index.html e indica che sta operando con il protocollo HTTP versione 1.0. La versione più recente di questo protocollo è la 1.1. L'esempio mostra che il server ha risposto inviando il contenuto del file index.html e poi ha chiuso la connessione, poiché si vede che il thread di lettura delle risposte si è concluso. Prima di inviare il contenuto del file index.html, il server web ha inviato una serie di intestazioni terminata da una riga vuota:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

La riga <html> è la prima riga del file /index.html. Quanto sopra è denominato intestazioni HTTP (HyperText Transfer Protocol). Non entreremo qui nei dettagli di queste intestazioni, ma ricordiamo che il nostro client generico ne consente l’accesso, il che può essere utile per comprenderle. Ad esempio, la prima riga:

<-- HTTP/1.1 200 OK

indica che il server Web contattato supporta il protocollo HTTP/1.1 e che ha effettivamente trovato il file richiesto (200 OK), dove 200 è un codice di risposta HTTP. Le righe

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

indicano al client che riceverà 11251 byte corrispondenti al testo HTML (HyperText Markup Language) e che, al termine dell’invio, la connessione verrà chiusa.

Abbiamo quindi un client TCP molto pratico. Senza dubbio fa meno del programma telnet che abbiamo utilizzato in precedenza, ma è stato interessante scriverlo da soli. Il programma del client TCP generico è il seguente:

// pacchetti importati
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // riceve come parametro le caratteristiche di un servizio nella forma
     // server porta
     // si connette al servizio
     // crea un thread per leggere i comandi digitati sulla tastiera
     // questi saranno inviati al server
     // crea un thread per leggere le risposte del server
     // queste verranno visualizzate sullo schermo
     // il tutto termina con il comando «fine» digitato sulla tastiera

   // variabile di istanza
  private static Socket client;

    public static void main(String[] args){

         // sintassi
        final String syntaxe="pg serveur port";

         // numero di argomenti
        if(args.length != 2)
            erreur(syntaxe,1);

         // si annota il nome del server
        String serveur=args[0];

         // la porta deve essere un numero intero >0
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[1]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

        client=null;
         // potrebbero verificarsi dei problemi
        try{
             // si effettua la connessione al servizio
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // errore
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // fine
            return;
        }//catch

         // si creano i thread di lettura/scrittura
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // fine thread principale
        return;
    }// main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // arresto con errore
        System.exit(exitCode);
    }//errore
}//classe  

class ClientSend extends Thread {
    // classe incaricata di leggere i comandi digitati sulla tastiera
     // e di inviarli a un server tramite un client TCP passato come parametro

    private Socket client;    // il client TCP

     // costruttore
    public ClientSend(Socket client){
         // si specifica il client TCP
        this.client=client;
    }//costruttore

     // metodo Run del thread
    public void run(){

        // dati locali
        PrintWriter OUT=null;            // flusso di scrittura di rete
    BufferedReader IN=null;        // flusso da tastiera
        String commande=null;            // comando letto dalla tastiera

         // gestione degli errori
        try{
             // creazione del flusso di scrittura di rete
            OUT=new PrintWriter(client.getOutputStream(),true);
      // creazione del flusso di input da tastiera
      IN=new BufferedReader(new InputStreamReader(System.in));
            // ciclo di immissione e invio dei comandi
            System.out.println("Commandes : ");
            while(true){
                 // lettura del comando digitato sulla tastiera
                commande=IN.readLine().trim();
                // finito?
                if (commande.toLowerCase().equals("fin")) break;
                // invio del comando al server
                OUT.println(commande);
                 // comando successivo
            }//while
        }catch(Exception ex){
             // errore
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//classe

class ClientReceive extends Thread{
    // classe incaricata di leggere le righe di testo destinate a un 
     // client TCP passato come parametro

    private Socket client;    // il client TCP

     // costruttore
    public ClientReceive(Socket client){
         // si registra il client TCP
        this.client=client;
    }//costruttore

     // metodo Run del thread
    public void run(){

        // dati locali
        BufferedReader IN=null;        // flusso di lettura di rete
        String réponse=null;        // risposta del server

         // gestione degli errori
        try{
             // creazione del flusso di lettura di rete
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // ciclo di lettura delle righe di testo del flusso IN
            while(true){
                 // lettura flusso di rete
                réponse=IN.readLine();
                 // flusso chiuso?
                if(réponse==null) break;
                // visualizzazione
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // errore
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//classe

9.2. Il server TCP generico

Ora ci occupiamo di un server

  • che visualizza sullo schermo i comandi inviati dai propri client
  • e invia loro in risposta le righe di testo digitate sulla tastiera da un utente. È quindi quest’ultimo a fungere da server.

Il programma viene avviato con: java serveurTCPgenerique portEcoute, dove portEcoute è la porta a cui i client devono connettersi. Il servizio al client sarà gestito da due thread:

  • un thread dedicato esclusivamente alla lettura delle righe di testo inviate dal client
  • un thread dedicato esclusivamente alla lettura delle risposte digitate dall’utente sulla tastiera. Quest’ultimo segnalerà, tramite il comando «fin», la chiusura della connessione con il client.

Il server crea due thread per ogni client. Se ci sono n client, ci saranno 2n thread attivi contemporaneamente. Il server, dal canto suo, non si arresta mai, a meno che l’utente non prema Ctrl-C sulla tastiera. Vediamo alcuni esempi.

Il server viene avviato sulla porta 100 e si utilizza il client generico per comunicare con esso. La finestra del client è la seguente:

E:\data\serge\MSNET\c#\rete\client tcp generico> java clientTCPgenerique localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

Le righe che iniziano con <-- sono quelle inviate dal server al client, le altre sono quelle inviate dal client al server. La finestra del server è la seguente:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]

Le righe che iniziano con <-- sono quelle inviate dal client al server. Le righe N: sono quelle inviate dal server al client n. N. Il server sopra indicato è ancora attivo, mentre il client 1 ha terminato l'operazione. Si avvia un secondo client per lo stesso server:


Dos> java clientTCPgenerique localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

La finestra del server appare quindi così:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

Simuliamo ora un server web avviando il nostro server generico sulla porta 88:


Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88

Apriamo ora un browser e richiediamo l'http://localhost:88/exemple.html URL. Il browser si connetterà quindi alla porta 88 del computer localhost e richiederà la pagina /exemple.html:

Image

Diamo ora un’occhiata alla finestra del nostro server:

Dos>java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Si scoprono così le intestazioni HTTP inviate dal browser. Questo ci permette di scoprire poco a poco il protocollo HTTP. In un esempio precedente, avevamo creato un client Web che inviava solo il comando GET. Era stato sufficiente. Qui vediamo che il browser invia altre informazioni al server. Il loro scopo è indicare al server quale tipo di client ha di fronte. Notiamo inoltre che le intestazioni HTTP terminano con una riga vuota.

Elaboriamo una risposta per il nostro client. L’utente alla tastiera è in questo caso il vero server e può elaborare una risposta manualmente. Ricordiamo la risposta fornita da un server Web in un esempio precedente:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

Proviamo a dare una risposta analoga:

...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]

Le righe che iniziano con 2: vengono inviate dal server al client n. 2. Il comando fin chiude la connessione tra il server e il client. Nella nostra risposta ci siamo limitati alle seguenti intestazioni HTTP:

HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :

Non specifichiamo la dimensione del file che stiamo per inviare (Content-Length), ma ci limitiamo a indicare che chiuderemo la connessione (Connection: close) dopo averlo inviato. Questo è sufficiente per il browser. Quando rileva che la connessione è stata chiusa, il browser capirà che la risposta del server è terminata e visualizzerà la pagina HTML che gli è stata inviata. Quest’ultima è la seguente:

2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>

L'utente chiude quindi la connessione al client digitando il comando fin. Il browser capisce così che la risposta del server è terminata e può quindi visualizzarla:

Image

Se, come sopra, si esegue il comando View/Source per vedere cosa ha ricevuto il browser, si ottiene:

Image

ovvero esattamente ciò che è stato inviato dal server generico.

Il codice del server TCP generico è il seguente:

// pacchetti
import java.io.*;
import java.net.*;

public class serveurTCPgenerique{

    // programma principale
    public static void main (String[] args){

    // riceve le richieste dei client sulla porta di ascolto
     // crea un thread per leggere le richieste del client
     // queste verranno visualizzate sullo schermo
     // crea un thread per leggere i comandi digitati sulla tastiera
     // questi saranno inviati come risposta al cliente
     // il tutto termina con il comando «fine» digitato sulla tastiera

    final String syntaxe="Syntaxe : pg port";
   // variabile di istanza
         // c'è un argomento
     if(args.length != 1)
        erreur(syntaxe,1);

         // la porta deve essere un numero intero >0
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // si crea il servizio di ascolto
    ServerSocket ecoute=null;
    int nbClients=0;    // numero di client gestiti
        try{
             // si crea il servizio
            ecoute=new ServerSocket(port);
             // monitoraggio
            System.out.println("Serveur générique lancé sur le port " + port);

             // ciclo di servizio ai clienti
            Socket client=null;
            while (true){ // ciclo infinito - verrà interrotto con Ctrl-C
                 // in attesa di un cliente
                client=ecoute.accept();

                 // il servizio è gestito da thread separati
                nbClients++;

                 // vengono creati i thread di lettura/scrittura
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // si torna ad ascoltare le richieste
            }// fine ciclo while
        }catch(Exception ex){
             // si segnala l'errore
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// fine main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // arresto con errore
        System.exit(exitCode);
    }//errore
}//classe

class ServeurSend extends Thread{
    // classe incaricata di leggere le risposte digitate sulla tastiera
     // e di inviarle a un client tramite un client TCP passato al costruttore

    Socket client;    // il client TCP
    int numClient;        // numero del client

     // costruttore
    public ServeurSend(Socket client, int numClient){
         // si annota il client TCP
        this.client=client;
         // e il relativo n.
        this.numClient=numClient;
    }//produttore

     // metodo Run del thread
    public void run(){

        // dati locali
        PrintWriter OUT=null;        // flusso di scrittura di rete
        String réponse=null;        // risposta letta dalla tastiera
    BufferedReader IN=null;    // flusso da tastiera

        // monitoraggio
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // gestione degli errori
        try{
             // creazione del flusso di scrittura di rete
            OUT=new PrintWriter(client.getOutputStream(),true);
      // creazione del flusso da tastiera
      IN=new BufferedReader(new InputStreamReader(System.in));
            // ciclo di immissione e invio dei comandi
            while(true){
                 // identificazione del cliente
                System.out.print("--> " + numClient + " : ");
                 // Lettura della risposta digitata sulla tastiera
                réponse=IN.readLine().trim();
                // finito?
                if (réponse.toLowerCase().equals("fin")) break;
                // invio della risposta al server
                OUT.println(réponse);
                 // risposta successiva
            }//while
        }catch(Exception ex){
             // errore
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//run
}//classe

class ServeurReceive extends Thread{
    // classe incaricata di leggere le righe di testo inviate al server 
     // tramite un client TCP passato al costruttore

    Socket client;    // il client TCP
    int numClient;        // numero del client

     // costruttore
    public ServeurReceive(Socket client, int numClient){
         // si registra il client TCP
        this.client=client;
         // e il suo n.
        this.numClient=numClient;
    }//produttore

     // metodo Run del thread
    public void run(){

        // dati locali
        BufferedReader IN=null;        // flusso di lettura di rete
        String réponse=null;        // risposta del server

         // monitoraggio
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // gestione degli errori
        try{
             // creazione del flusso di lettura di rete
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // ciclo di lettura delle righe di testo del flusso IN
            while(true){
                 // lettura flusso di rete
                réponse=IN.readLine();
                 // flusso chiuso?
                if(réponse==null) break;
                // visualizzazione
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // errore
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//classe