Skip to content

9. Code source de programmes

9.1. Le client TCP générique

Beaucoup de services créés à l'origine de l'Internet fonctionnent selon le modèle du serveur d'écho étudié précédemment : les échanges client-serveur se font pas échanges de lignes de texte. Nous allons écrire un client tcp générique qui sera lancé de la façon suivante : java cltTCPgenerique serveur port

Ce client TCP se connectera sur le port port du serveur serveur. Ceci fait, il créera deux threads :

  1. un thread chargé de lire des commandes tapées au clavier et de les envoyer au serveur
  2. un thread chargé de lire les réponses du serveur et de les afficher à l'écran

Pourquoi deux threads? Chaque service TCP-IP a son protocole particulier et on trouve parfois les situations suivantes :

  • le client doit envoyer plusieurs lignes de texte avant d'avoir une réponse
  • la réponse d'un serveur peut comporter plusieurs lignes de texte

Aussi la boucle envoi d'une unique ligne au serveur - réception d'une unique ligne envoyée par le serveur ne convient-elle pas toujours. On va donc créer deux boucles dissociées :

  • une boucle de lecture des commandes tapées au clavier pour être envoyées au serveur. L'utilisateur signalera la fin des commandes avec le mot clé fin.
  • une boucle de réception et d'affichage des réponses du serveur. Celle-ci sera une boucle infinie qui ne sera interrompue que par la fermeture du flux réseau par le serveur ou par l'utilisateur au clavier qui tapera la commande fin.

Pour avoir ces deux boucles dissociées, il nous faut deux threads indépendants. Montrons un exemple d'excécution où notre client tcp générique se connecte à un service SMTP (SendMail Transfer Protocol). Ce service est responsable de l'acheminement du courrier électronique à leurs destinataires. Il fonctionne sur le port 25 et a un protocole de dialogue de type échanges de lignes de texte.


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]

Commentons ces échanges client-serveur :

  • le service SMTP envoie un message de bienvenue lorsqu'un client se connecte à lui :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • certains services ont une commande help donnant des indications sur les commandes utilisables avec le service. Ici ce n'est pas le cas. Les commandes SMTP utilisées dans l'exemple sont les suivantes :
    • mail from: expéditeur, pour indiquer l'adresse électronique de l'expéditeur du message
    • rcpt to: destinataire, pour indiquer l'adresse électronique du destinataire du message. S'il y a plusieurs destinataires, on ré-émet autant de fois que nécessaire la commande rcpt to: pour chacun des destinataires.
    • data qui signale au serveur SMTP qu'on va envoyer le message. Comme indiqué dans la réponse du serveur, celui-ci est une suite de lignes terminée par une ligne contenant le seul caractère point. Un message peut avoir des entêtes séparés du corps du message par une ligne vide. Dans notre exemple, nous avons mis un sujet avec le mot clé Subject:
  • une fois le message envoyé, on peut indiquer au serveur qu'on a terminé avec la commande quit. Le serveur ferme alors la connexion réseau. Le thread de lecture peut détecter cet événement et s'arrêter.
  • l'utilisateur tape alors fin au clavier pour arrêter également le thread de lecture des commandes tapées au clavier.

Si on vérifie le courrier reçu, nous avons la chose suivante (Outlook) :

Image

On remarquera que le service SMTP ne peut détecter si un expéditeur est valide ou non. Aussi ne peut-on jamais faire confiance au champ from d'un message. Ici l'expéditeur machin@univ-angers.fr n'existait pas.

Ce client tcp générique peut nous permettre de découvrir le protocole de dialogue de services internet et à partir de là construire des classes spécialisées pour des clients de ces services. Découvrons le protocole de dialogue du service POP (Post Office Protocol) qui permet de retrouver ses méls stockés sur un serveur. Il travaille sur le port 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]

Les principales commandes sont les suivantes :

  • user login, où on donne son login sur la machine qui détient nos méls
  • pass password, où on donne le mot de passe associé au login précédent
  • list, pour avoir la liste des messages sous la forme numéro, taille en octets
  • retr i, pour lire le message n° i
  • quit, pour arrêter le dialogue.

Découvrons maintenant le protocole de dialogue entre un client et un serveur Web qui lui travaille habituellement sur le port 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 envoie ses commandes au serveur selon le schéma suivant :

commande1
commande2
...
commanden
[ligne vide]

Ce n'est qu'après avoir reçu la ligne vide que le serveur Web répond. Dans l'exemple nous n'avons utilisé qu'une commande :

GET /index.html HTTP/1.0

qui demande au serveur l'URL /index.html et indique qu'il travaille avec le protocole HTTP version 1.0. La version la plus récente de ce protocole est 1.1. L'exemple montre que le serveur a répondu en renvoyant le contenu du fichier index.html puis qu'il a fermé la connexion puisqu'on voit le thread de lecture des réponses se terminer. Avant d'envoyer le contenu du fichier index.html, le serveur web a envoyé une série d'entêtes terminée par une ligne vide :

<-- 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 ligne <html> est la première ligne du fichier /index.html. Ce qui précède s'appelle des entêtes HTTP (HyperText Transfer Protocol). Nous n'allons pas détailler ici ces entêtes mais on se rappellera que notre client générique y donne accès, ce qui peut être utile pour les comprendre. La première ligne par exemple :

<-- HTTP/1.1 200 OK

indique que le serveur Web contacté comprend le protocole HTTP/1.1 et qu'il a bien trouvé le fichier demandé (200 OK), 200 étant un code de réponse HTTP. Les lignes

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

disent au client qu'il va recevoir 11251 octets représentant du texte HTML (HyperText Markup Language) et qu'à la fin de l'envoi, la connexion sera fermée.

On a donc là un client tcp très pratique. Il fait sans doute moins que le programme telnet que nous avons utilisé précédemment mais il était intéressant de l'écrire nous-mêmes. Le programme du client tcp générique est le suivant :

// paquetages importés
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // reçoit en paramètre les caractéristiques d'un service sous la forme
    // serveur port
    // se connecte au service
    // crée un thread pour lire des commandes tapées au clavier
    // celles-ci seront envoyées au serveur
    // crée un thread pour lire les réponses du serveur
    // celles-ci seront affichées à l'écran
    // le tout se termine avec la commande fin tapée au clavier

  // variable d'instance
  private static Socket client;

    public static void main(String[] args){

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

        // nombre d'arguments
        if(args.length != 2)
            erreur(syntaxe,1);

        // on note le nom du serveur
        String serveur=args[0];

        // le port doit être entier >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;
        // il peut y avoir des problèmes
        try{
            // on se connecte au service
            client=new Socket(serveur,port);
        }catch(Exception ex){
            // erreur
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
            // fin
            return;
        }//catch

        // on crée les threads de lecture/écriture
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // fin thread main
        return;
    }// main

    // affichage des erreurs
    public static void erreur(String msg, int exitCode){
        // affichage erreur
        System.err.println(msg);
        // arrêt avec erreur
        System.exit(exitCode);
    }//erreur
}//classe  

class ClientSend extends Thread {
    // classe chargée de lire des commandes tapées au clavier
    // et de les envoyer à un serveur via un client tcp passé en paramètre

    private Socket client;  // le client tcp

    // constructeur
    public ClientSend(Socket client){
        // on note le client tcp
        this.client=client;
    }//constructeur

    // méthode Run du thread
    public void run(){

        // données locales
        PrintWriter OUT=null;           // flux d'écriture réseau
    BufferedReader IN=null;     // flux clavier
        String commande=null;           // commande lue au clavier

        // gestion des erreurs
        try{
            // création du flux d'écriture réseau
            OUT=new PrintWriter(client.getOutputStream(),true);
      // création du flux d'entrée clavier
      IN=new BufferedReader(new InputStreamReader(System.in));
            // boucle saisie-envoi des commandes
            System.out.println("Commandes : ");
            while(true){
                // lecture commande tapée au clavier
                commande=IN.readLine().trim();
                // fini ?
                if (commande.toLowerCase().equals("fin")) break;
                // envoi commande au serveur
                OUT.println(commande);
                // commande suivante
            }//while
        }catch(Exception ex){
            // erreur
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
        // fin - on ferme les flux
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
        // on signale la fin du thread
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//classe

class ClientReceive extends Thread{
    // classe chargée de lire les lignes de texte destinées à un 
    // client tcp passé en paramètre

    private Socket client;  // le client tcp

    // constructeur
    public ClientReceive(Socket client){
        // on note le client tcp
        this.client=client;
    }//constructeur

    // méthode Run du thread
    public void run(){

        // données locales
        BufferedReader IN=null;     // flux lecture réseau
        String réponse=null;        // réponse serveur

        // gestion des erreurs
        try{
            // création du flux lecture réseau
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // boucle lecture lignes de texte du flux IN
            while(true){
                // lecture flux réseau
                réponse=IN.readLine();
                // flux fermé ?
                if(réponse==null) break;
                // affichage
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // erreur
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
        // fin - on ferme les flux
        try{
            IN.close();client.close();
        }catch(Exception ex){}
        // on signale la fin du thread
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//classe

9.2. Le serveur Tcp générique

Maintenant nous nous intéressons à un serveur

  • qui affiche à l'écran les commandes envoyées par ses clients
  • leur envoie comme réponse les lignes de texte tapées au clavier par un utilisateur. C'est donc ce dernier qui fait office de serveur.

Le programme est lancé par : java serveurTCPgenerique portEcoute, où portEcoute est le port sur lequel les clients doivent se connecter. Le service au client sera assuré par deux threads :

  • un thread se consacrant exclusivement à la lecture des lignes de texte envoyées par le client
  • un thread se consacrant exclusivement à la lecture des réponses tapées au clavier par l'utilisateur. Celui-ci signalera par la commande fin qu'il clôt la connexion avec le client.

Le serveur crée deux threads par client. S'il y a n clients, il y aura 2n threads actifs en même temps. Le serveur lui ne s'arrête jamais sauf par un Ctrl-C tapé au clavier par l'utilisateur. Voyons quelques exemples.

Le serveur est lancé sur le port 100 et on utilise le client générique pour lui parler. La fenêtre du client est la suivante :


E:\data\serge\MSNET\c#\réseau\client tcp générique> 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]

Les lignes commençant par <-- sont celles envoyées du serveur au client, les autres celles du client vers le serveur. La fenêtre du serveur est la suivante :


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]

Les lignes commençant par <-- sont celles envoyées du client au serveur. Les lignes N : sont les lignes envoyées du serveur au client n° N. Le serveur ci-dessus est encore actif alors que le client 1 est terminé. On lance un second client pour le même serveur :


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 fenêtre du serveur est alors celle-ci :


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

Simulons maintenant un serveur web en lançant notre serveur générique sur le port 88 :


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

Prenons maintenant un navigateur et demandons l'URL http://localhost:88/exemple.html. Le navigateur va alors se connecter sur le port 88 de la machine localhost puis demander la page /exemple.html :

Image

Regardons maintenant la fenêtre de notre serveur :

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
<--

On découvre ainsi les entêtes HTTP envoyés par le navigateur. Cela nous permet de découvrir peu à peu le protocole HTTP. Lors d'un précédent exemple, nous avions créé un client Web qui n'envoyait que la seule commande GET. Cela avait été suffisant. On voit ici que le navigateur envoie d'autres informations au serveur. Elles ont pour but d'indiquer au serveur quel type de client il a en face de lui. On voit aussi que les entêtes HTTP se terminent par une ligne vide.

Elaborons une réponse à notre client. L'utilisateur au clavier est ici le véritable serveur et il peut élaborer une réponse à la main. Rappelons-nous la réponse faite par un serveur Web dans un précédent exemple :

<-- 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>

Essayons de donner une réponse analogue :

...
<-- 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]

Les lignes commençant par 2 : sont envoyées du serveur au client n° 2. La commande fin clôt la connexion du serveur au client. Nous nous sommes limités dans notre réponse aux entêtes HTTP suivants :

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

Nous ne donnons pas la taille du fichier que nous allons envoyer (Content-Length) mais nous contentons de dire que nous allons fermer la connexion (Connection: close) après envoi de celui-ci. Cela est suffisant pour le navigateur. En voyant la connexion fermée, il saura que la réponse du serveur est terminée et affichera la page HTML qui lui a été envoyée. Cette dernière est la suivante :

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'utilisateur ferme ensuite la connexion au client en tapant la commande fin. Le navigateur sait alors que la réponse du serveur est terminée et peut alors l'afficher :

Image

Si ci-dessus, on fait View/Source pour voir ce qu'a reçu le navigateur, on obtient :

Image

c'est à dire exactement ce qu'on a envoyé depuis le serveur générique.

Le code du serveur tcp générique est le suivant :

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

public class serveurTCPgenerique{

    // programme principal
    public static void main (String[] args){

    // reçoit le port d'écoute des demandes des clients
    // crée un thread pour lire les demandes du client
    // celles-ci seront affichées à l'écran
    // crée un thread pour lire des commandes tapées au clavier
    // celles-ci seront envoyées comme réponse au client
    // le tout se termine avec la commande fin tapée au clavier

    final String syntaxe="Syntaxe : pg port";
  // variable d'instance
         // y-a-t-il un argument
     if(args.length != 1)
        erreur(syntaxe,1);

        // le port doit être entier >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);

     // on crée le servive d'écoute
    ServerSocket ecoute=null;
    int nbClients=0;    // nbre de clients traités
        try{
            // on crée le service
            ecoute=new ServerSocket(port);
            // suivi
            System.out.println("Serveur générique lancé sur le port " + port);

            // boucle de service aux clients
            Socket client=null;
            while (true){ // boucle infinie - sera arrêtée par Ctrl-C
                // attente d'un client
                client=ecoute.accept();

                // le service est assuré des threads séparés
                nbClients++;

                // on crée les threads de lecture/écriture
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // on retourne à l'écoute des demandes
            }// fin while
        }catch(Exception ex){
            // on signale l'erreur
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// fin main

    // affichage des erreurs
    public static void erreur(String msg, int exitCode){
        // affichage erreur
        System.err.println(msg);
        // arrêt avec erreur
        System.exit(exitCode);
    }//erreur
}//classe

class ServeurSend extends Thread{
    // classe chargée de lire des réponses tapées au clavier
    // et de les envoyer à un client via un client tcp passé au constructeur

    Socket client;  // le client tcp
    int numClient;      // n° de client

    // constructeur
    public ServeurSend(Socket client, int numClient){
        // on note le client tcp
        this.client=client;
        // et son n°
        this.numClient=numClient;
    }//constructeur

    // méthode Run du thread
    public void run(){

        // données locales
        PrintWriter OUT=null;       // flux d'écriture réseau
        String réponse=null;        // réponse lue au clavier
    BufferedReader IN=null; // flux clavier

        // suivi
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
        // gestion des erreurs
        try{
            // création du flux d'écriture réseau
            OUT=new PrintWriter(client.getOutputStream(),true);
      // création du flux clavier
      IN=new BufferedReader(new InputStreamReader(System.in));
            // boucle saisie-envoi des commandes
            while(true){
                // identification client
                System.out.print("--> " + numClient + " : ");
                // lecture réponse tapée au clavier
                réponse=IN.readLine().trim();
                // fini ?
                if (réponse.toLowerCase().equals("fin")) break;
                // envoi réponse au serveur
                OUT.println(réponse);
                // réponse suivante
            }//while
        }catch(Exception ex){
            // erreur
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
        // fin - on ferme les flux
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
        // on signale la fin du thread
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//run
}//classe

class ServeurReceive extends Thread{
    // classe chargée de lire les lignes de texte envoyées au serveur 
    // via un client tcp passé au constructeur

    Socket client;  // le client tcp
    int numClient;      // n° de client

    // constructeur
    public ServeurReceive(Socket client, int numClient){
        // on note le client tcp
        this.client=client;
        // et son n°
        this.numClient=numClient;
    }//constructeur

    // méthode Run du thread
    public void run(){

        // données locales
        BufferedReader IN=null;     // flux lecture réseau
        String réponse=null;        // réponse serveur

        // suivi
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
        // gestion des erreurs
        try{
            // création du flux lecture réseau
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // boucle lecture lignes de texte du flux IN
            while(true){
                // lecture flux réseau
                réponse=IN.readLine();
                // flux fermé ?
                if(réponse==null) break;
                // affichage
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // erreur
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
        // fin - on ferme les flux
        try{
            IN.close();client.close();
        }catch(Exception ex){}
        // on signale la fin du thread
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//classe