Skip to content

9. Broncode van programma’s

9.1. De generieke client TCP

Veel diensten die in de beginjaren van het internet zijn ontstaan, werken volgens het eerder besproken echo-servermodel: de communicatie tussen client en server verloopt via de uitwisseling van tekstregels. We gaan een generieke TCP-client schrijven die als volgt wordt gestart: java cltTCPgenerique server poort

Deze client TCP maakt verbinding met poort port van de server serveur. Vervolgens maakt hij twee threads aan:

  1. een thread die de op het toetsenbord ingevoerde opdrachten leest en deze naar de server verstuurt
  2. een thread die de antwoorden van de server leest en deze op het scherm weergeeft

Waarom twee threads? Elke TCP-IP-service heeft zijn eigen specifieke protocol en soms doen zich de volgende situaties voor:

  • de client moet meerdere regels tekst verzenden voordat hij een antwoord krijgt
  • het antwoord van een server kan uit meerdere regels tekst bestaan

Daarom is de lus waarbij één regel naar de server wordt verzonden en vervolgens één regel van de server wordt ontvangen, niet altijd geschikt. We gaan daarom twee afzonderlijke lussen maken:

  • een lus voor het lezen van de op het toetsenbord ingevoerde opdrachten die naar de server moeten worden verzonden. De gebruiker geeft het einde van de opdrachten aan met het trefwoord fin.
  • een lus voor het ontvangen en weergeven van de antwoorden van de server. Dit wordt een oneindige lus die alleen wordt onderbroken wanneer de server de netwerkverbinding verbreekt of wanneer de gebruiker via het toetsenbord het commando fin invoert.

Om deze twee lussen van elkaar te scheiden, hebben we twee onafhankelijke threads nodig. Laten we een uitvoeringsvoorbeeld bekijken waarin onze generieke TCP-client verbinding maakt met een SMTP-service (SendMail Transfer Protocol). Deze service is verantwoordelijk voor het doorsturen van e-mail naar de ontvangers. De service draait op poort 25 en maakt gebruik van een communicatieprotocol waarbij tekstregels worden uitgewisseld.


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]

Laten we deze client-server-uitwisselingen eens bekijken:

  • de service SMTP verstuurt een welkomstbericht wanneer een client verbinding met hem maakt:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • sommige services hebben een commando help dat informatie geeft over de commando's die met de service kunnen worden gebruikt. Hier is dat niet het geval. De commando's SMTP die in het voorbeeld worden gebruikt, zijn de volgende:
    • mail from: expéditeur, om het e-mailadres van de afzender van het bericht aan te geven
    • rcpt to: destinataire, om het e-mailadres van de ontvanger van het bericht aan te geven. Als er meerdere ontvangers zijn, wordt het commando rcpt to: zo vaak als nodig is herhaald voor elke ontvanger.
    • data, waarmee aan de server SMTP wordt aangegeven dat het bericht wordt verzonden. Zoals aangegeven in het antwoord van de server, bestaat dit uit een reeks regels die eindigt met een regel die uitsluitend uit een punt bestaat. Een bericht kan headers bevatten die door een lege regel van de hoofdtekst worden gescheiden. In ons voorbeeld hebben we een onderwerp opgegeven met het trefwoord Subject:
  • zodra het bericht is verzonden, kan men de server laten weten dat men klaar is met het commando quit. De server verbreekt dan de netwerkverbinding. De leesthread kan deze gebeurtenis detecteren en stoppen.
  • De gebruiker typt vervolgens ‘fin’ op het toetsenbord om ook de thread die de ingevoerde opdrachten leest, te stoppen.

Als we de ontvangen e-mail controleren, zien we het volgende (Outlook):

Image

Merk op dat de dienst SMTP niet kan vaststellen of een afzender geldig is of niet. Daarom kun je het veld from in een bericht nooit vertrouwen. In dit geval bestond de afzender machin@univ-angers.fr niet.

Met deze generieke TCP-client kunnen we het communicatieprotocol van internetdiensten achterhalen en op basis daarvan gespecialiseerde klassen bouwen voor clients van deze diensten. Laten we het communicatieprotocol van de dienst POP (Post Office Protocol) onderzoeken, waarmee e-mails die op een server zijn opgeslagen, kunnen worden opgehaald. Deze dienst werkt op poort 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]

De belangrijkste commando's zijn de volgende:

  • user login, waarbij je je login opgeeft op de machine waarop je e-mails staan
  • pass password, waarbij je het wachtwoord opgeeft dat bij de vorige gebruikersnaam hoort
  • list, om de lijst met berichten te krijgen in de vorm van nummer en grootte in bytes
  • retr i, om bericht nr. i te lezen
  • quit, om de dialoog te beëindigen.

Laten we nu eens kijken naar het communicatieprotocol tussen een client en een webserver, die doorgaans op poort 80 draait:


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]

Een webclient stuurt zijn opdrachten naar de server volgens het volgende schema:

commande1
commande2
...
commanden
[ligne vide]

Pas nadat de lege regel is ontvangen, reageert de webserver. In het voorbeeld hebben we slechts één commando gebruikt:

GET /index.html HTTP/1.0

waarmee de server wordt gevraagd om URL /index.html en wordt aangegeven dat er wordt gewerkt met het protocol HTTP versie 1.0. De meest recente versie van dit protocol is 1.1. Het voorbeeld laat zien dat de server heeft gereageerd door de inhoud van het bestand index.html terug te sturen en vervolgens de verbinding heeft verbroken, aangezien we zien dat de thread voor het lezen van de antwoorden is beëindigd. Voordat de inhoud van het bestand index.html werd verzonden, heeft de webserver een reeks headers verzonden, afgesloten met een lege regel:

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

De regel <html> is de eerste regel van het bestand /index.html. Het bovenstaande wordt HTTP-headers genoemd (HyperText Transfer Protocol). We gaan hier niet in detail op deze headers ingaan, maar onthoud dat onze generieke client hier toegang toe biedt, wat nuttig kan zijn om ze te begrijpen. De eerste regel bijvoorbeeld:

<-- HTTP/1.1 200 OK

geeft aan dat de benaderde webserver het protocol HTTP/1.1 ondersteunt en dat hij het gevraagde bestand inderdaad heeft gevonden (200 OK), waarbij 200 een HTTP-antwoordcode is. De regels

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

geven de client door dat hij 11251 bytes zal ontvangen die de tekst HTML (HyperText Markup Language) vertegenwoordigen en dat de verbinding aan het einde van de verzending zal worden verbroken.

Dit is dus een erg handige TCP-client. Hij doet ongetwijfeld minder dan het programma telnet dat we eerder hebben gebruikt, maar het was interessant om hem zelf te schrijven. Het programma van de generieke TCP-client is als volgt:

// geïmporteerde pakketten
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // ontvangt als parameter de kenmerken van een service in de vorm
     // serverpoort
     // maakt verbinding met de dienst
     // maakt een thread aan om op het toetsenbord ingevoerde opdrachten te lezen
     // deze worden naar de server verzonden
     // maakt een thread aan om de antwoorden van de server te lezen
     // deze worden op het scherm weergegeven
     // het geheel wordt beëindigd met het commando 'fin' dat via het toetsenbord wordt ingevoerd

   // instantievariabele
  private static Socket client;

    public static void main(String[] args){

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

         // aantal argumenten
        if(args.length != 2)
            erreur(syntaxe,1);

         // de naam van de server wordt genoteerd
        String serveur=args[0];

         // de poort moet een geheel getal >0 zijn
        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;
         // er kunnen problemen optreden
        try{
             // er wordt verbinding gemaakt met de service
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // fout
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // einde
            return;
        }//catch

         // de lees- en schrijfthreads worden aangemaakt
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // einde hoofdthread
        return;
    }// main

     // foutmeldingen weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse  

class ClientSend extends Thread {
    // klasse die verantwoordelijk is voor het lezen van via het toetsenbord ingevoerde opdrachten
     // en deze via een als parameter doorgegeven TCP-client naar een server te verzenden

    private Socket client;    // de TCP-client

     // constructor
    public ClientSend(Socket client){
         // hier wordt de TCP-client opgegeven
        this.client=client;
    }//constructor

     // de Run-methode van de thread
    public void run(){

        // lokale gegevens
        PrintWriter OUT=null;            // netwerk-schrijfstroom
    BufferedReader IN=null;        // toetsenbordstroom
        String commande=null;            // op het toetsenbord ingevoerde opdracht

         // foutbeheer
        try{
             // aanmaken van de netwerkschrijfstroom
            OUT=new PrintWriter(client.getOutputStream(),true);
      // aanmaken van de toetsenbordinvoerstroom
      IN=new BufferedReader(new InputStreamReader(System.in));
            // lus voor invoer en verzending van opdrachten
            System.out.println("Commandes : ");
            while(true){
                 // lezen van via het toetsenbord ingevoerde opdracht
                commande=IN.readLine().trim();
                // klaar?
                if (commande.toLowerCase().equals("fin")) break;
                // commando naar de server verzenden
                OUT.println(commande);
                 // volgend commando
            }//while
        }catch(Exception ex){
             // fout
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde - de streams worden gesloten
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//klasse

class ClientReceive extends Thread{
    // klasse die verantwoordelijk is voor het lezen van de tekstregels bestemd voor een 
     // TCP-client die als parameter is doorgegeven

    private Socket client;    // de TCP-client

     // constructor
    public ClientReceive(Socket client){
         // de TCP-client wordt geregistreerd
        this.client=client;
    }//constructor

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        BufferedReader IN=null;        // netwerkleesstroom
        String réponse=null;        // serverantwoord

         // foutbeheer
        try{
             // aanmaken van de netwerkleesstroom
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // lus voor het lezen van tekstregels uit de IN-stream
            while(true){
                 // netwerkstream lezen
                réponse=IN.readLine();
                 // stream gesloten?
                if(réponse==null) break;
                // weergave
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // fout
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde – de streams worden gesloten
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//klasse

9.2. De generieke TCP-server

Nu richten we ons op een server

  • die de door zijn clients verzonden commando's op het scherm weergeeft
  • en als antwoord de tekstregels terugstuurt die door een gebruiker via het toetsenbord zijn ingevoerd. De gebruiker fungeert dus als server.

Het programma wordt gestart met: java serveurTCPgenerique portEcoute, waarbij portEcoute de poort is waarop de clients verbinding moeten maken. De dienstverlening aan de client wordt verzorgd door twee threads:

  • een thread die zich uitsluitend bezighoudt met het lezen van de door de client verzonden tekstregels
  • een thread die zich uitsluitend bezighoudt met het lezen van de antwoorden die de gebruiker via het toetsenbord invoert. Deze thread geeft met het commando `fin` aan dat de verbinding met de client wordt beëindigd.

De server maakt twee threads per klant aan. Als er n klanten zijn, zullen er 2n threads tegelijkertijd actief zijn. De server stopt zelf nooit, tenzij de gebruiker op het toetsenbord op Ctrl-C drukt. Laten we enkele voorbeelden bekijken.

De server wordt gestart op poort 100 en we gebruiken de generieke client om met de server te communiceren. Het venster van de client ziet er als volgt uit:

E:\data\serge\MSNET\c#\netwerk\generieke tcp-client> 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]

De regels die beginnen met <-- zijn de regels die van de server naar de client worden verzonden, de andere zijn die van de client naar de server. Het servervenster ziet er als volgt uit:


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]

De regels die beginnen met <-- zijn de regels die van de client naar de server zijn verzonden. De regels N: zijn de regels die van de server naar client nr. N zijn verzonden. De bovenstaande server is nog steeds actief, terwijl client 1 is beëindigd. We starten een tweede client voor dezelfde 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]

Het servervenster ziet er dan als volgt uit:


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

Laten we nu een webserver simuleren door onze generieke server op poort 88 te starten:


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

Laten we nu een browser openen en de pagina URL http://localhost:88/exemple.html opvragen. De browser maakt vervolgens verbinding met poort 88 van de machine localhost en vraagt vervolgens de pagina /exemple.html op:

Image

Laten we nu eens kijken naar het venster van onze 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
<--

Zo ontdekken we de headers HTTP die door de browser worden verzonden. Hierdoor kunnen we het protocol HTTP beetje bij beetje ontrafelen. In een eerder voorbeeld hadden we een webclient gemaakt die slechts één commando, namelijk GET, verstuurde. Dat was toen voldoende. We zien hier dat de browser nog andere informatie naar de server stuurt. Deze informatie is bedoeld om de server te laten weten met welk type client hij te maken heeft. We zien ook dat de headers HTTP eindigen met een lege regel.

Laten we een antwoord voor onze client opstellen. De gebruiker achter het toetsenbord is hier de echte server en kan met de hand een antwoord opstellen. Laten we ons het antwoord van een webserver in een eerder voorbeeld nog eens in herinnering brengen:

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

Laten we eens proberen een soortgelijk antwoord te geven:

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

Regels die beginnen met 2: worden vanaf de server naar client nr. 2 verzonden. Het commando fin verbreekt de verbinding tussen de server en de client. In ons antwoord hebben we ons beperkt tot de volgende HTTP-headers:

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

We geven de bestandsgrootte van het bestand dat we gaan verzenden (Content-Length) niet aan, maar we vermelden alleen dat we de verbinding zullen verbreken (Connection: close) nadat dit bestand is verzonden. Dit is voldoende voor de browser. Zodra de verbinding wordt verbroken, weet de browser dat het antwoord van de server is voltooid en geeft hij de pagina HTML weer die naar hem is verzonden. Deze pagina ziet er als volgt uit:

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>

De gebruiker verbreekt vervolgens de verbinding met de client door het commando fin in te voeren. De browser weet dan dat het antwoord van de server is voltooid en kan het vervolgens weergeven:

Image

Als we hierboven View/Source invoeren om te zien wat de browser heeft ontvangen, krijgen we:

Image

dat wil zeggen precies wat we vanaf de generieke server hebben verzonden.

De code van de generieke TCP-server is als volgt:

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

public class serveurTCPgenerique{

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

    // ontvangt verzoeken van clients via de luisterpoort
     // maakt een thread aan om de verzoeken van de client te lezen
     // deze worden op het scherm weergegeven
     // maakt een thread aan om op het toetsenbord ingevoerde opdrachten te lezen
     // deze worden als antwoord naar de klant verzonden
     // het geheel wordt beëindigd met het commando 'fin' dat via het toetsenbord wordt ingevoerd

    final String syntaxe="Syntaxe : pg port";
   // instantievariabele
         // is er een argument
     if(args.length != 1)
        erreur(syntaxe,1);

         // de poort moet een geheel getal >0 zijn
        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);

     // de luisterdienst wordt aangemaakt
    ServerSocket ecoute=null;
    int nbClients=0;    // aantal verwerkte clients
        try{
             // de service wordt aangemaakt
            ecoute=new ServerSocket(port);
             // opvolging
            System.out.println("Serveur générique lancé sur le port " + port);

             // lus voor klantenservice
            Socket client=null;
            while (true){ // oneindige lus  wordt gestopt met Ctrl-C
                 // wachten op een klant
                client=ecoute.accept();

                 // de service wordt uitgevoerd door afzonderlijke threads
                nbClients++;

                 // lees-/schrijfthreads worden aangemaakt
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // we gaan weer over tot het afwachten van verzoeken
            }// einde while
        }catch(Exception ex){
             // de fout wordt gemeld
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// einde main

     // fouten weergeven
    public static void erreur(String msg, int exitCode){
         // fout weergeven
        System.err.println(msg);
         // afsluiten met fout
        System.exit(exitCode);
    }//fout
}//klasse

class ServeurSend extends Thread{
    // klasse die verantwoordelijk is voor het lezen van via het toetsenbord ingevoerde antwoorden
     // en deze via een aan de constructor doorgegeven TCP-client naar een client te verzenden

    Socket client;    // de TCP-client
    int numClient;        // clientnummer

     // constructor
    public ServeurSend(Socket client, int numClient){
         // we noteren de TCP-client
        this.client=client;
         // en het nummer ervan
        this.numClient=numClient;
    }//fabrikant

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        PrintWriter OUT=null;        // netwerk-schrijfstroom
        String réponse=null;        // van het toetsenbord gelezen antwoord
    BufferedReader IN=null;    // toetsenbordstroom

        // tracking
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // foutbeheer
        try{
             // aanmaken van de netwerkschrijfstroom
            OUT=new PrintWriter(client.getOutputStream(),true);
      // aanmaken van de toetsenbordstroom
      IN=new BufferedReader(new InputStreamReader(System.in));
            // lus voor invoer en verzending van opdrachten
            while(true){
                 // klantidentificatie
                System.out.print("--> " + numClient + " : ");
                 // uitlezen van het via het toetsenbord ingevoerde antwoord
                réponse=IN.readLine().trim();
                // klaar?
                if (réponse.toLowerCase().equals("fin")) break;
                // antwoord naar de server verzenden
                OUT.println(réponse);
                 // volgend antwoord
            }//while
        }catch(Exception ex){
             // fout
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde - de streams worden gesloten
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//uitvoeren
}//klasse

class ServeurReceive extends Thread{
    // klasse die verantwoordelijk is voor het lezen van de naar de server verzonden tekstregels 
     // via een TCP-client die aan de constructor wordt doorgegeven

    Socket client;    // de TCP-client
    int numClient;        // clientnummer

     // constructor
    public ServeurReceive(Socket client, int numClient){
         // de TCP-client wordt genoteerd
        this.client=client;
         // en het nummer ervan
        this.numClient=numClient;
    }//fabrikant

     // Run-methode van de thread
    public void run(){

        // lokale gegevens
        BufferedReader IN=null;        // netwerkleesstroom
        String réponse=null;        // serverantwoord

         // tracking
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // foutbeheer
        try{
             // aanmaken van de netwerkleesstroom
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // lus voor het lezen van tekstregels uit de stream IN
            while(true){
                 // netwerkstream lezen
                réponse=IN.readLine();
                 // stream gesloten?
                if(réponse==null) break;
                // weergave
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // fout
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // einde  de streams worden gesloten
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // het einde van de thread wordt gemeld
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//klasse