Skip to content

9. Kod źródłowy programów

9.1. Ogólny klient TCP

Wiele usług powstałych w początkach Internetu działa zgodnie z modelem serwera echo, który omówiliśmy wcześniej: komunikacja między klientem a serwerem odbywa się poprzez wymianę wierszy tekstu. Napiszemy ogólny klient TCP, który będzie uruchamiany w następujący sposób: java cltTCPgenerique serwer port

Ten klient TCP połączy się z portem port serwera serveur. Następnie utworzy dwa wątki:

  1. wątek odpowiedzialny za odczytywanie poleceń wpisywanych z klawiatury i wysyłanie ich do serwera
  2. wątek odpowiedzialny za odczytywanie odpowiedzi z serwera i wyświetlanie ich na ekranie

Dlaczego dwa wątki? Każda usługa TCP-IP ma swój własny protokół i czasami zdarzają się następujące sytuacje:

  • klient musi wysłać kilka wierszy tekstu, zanim otrzyma odpowiedź
  • odpowiedź serwera może zawierać kilka wierszy tekstu

Dlatego pętla polegająca na wysyłaniu pojedynczego wiersza do serwera – a następnie odbieraniu pojedynczego wiersza wysłanego przez serwer – nie zawsze jest odpowiednia. Stworzymy zatem dwie oddzielne pętle:

  • pętlę odczytu poleceń wpisywanych z klawiatury w celu wysłania ich do serwera. Użytkownik zasygnalizuje koniec poleceń za pomocą słowa kluczowego fin.
  • pętlę odbierania i wyświetlania odpowiedzi z serwera. Będzie to pętla nieskończona, która zostanie przerwana jedynie przez zamknięcie połączenia sieciowego przez serwer lub przez użytkownika za pomocą klawiatury, który wpisze polecenie fin.

Aby te dwie pętle działały niezależnie, potrzebujemy dwóch niezależnych wątków. Przedstawmy przykład wykonania, w którym nasz generyczny klient TCP łączy się z usługą SMTP (SendMail Transfer Protocol). Usługa ta odpowiada za dostarczanie wiadomości e-mail do odbiorców. Działa ona na porcie 25 i wykorzystuje protokół komunikacyjny oparty na wymianie wierszy tekstowych.


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]

Omówmy tę wymianę danych między klientem a serwerem:

  • usługa SMTP wysyła wiadomość powitalną, gdy klient nawiązuje z nią połączenie:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • niektóre usługi posiadają polecenie help, które zawiera informacje o poleceniach dostępnych w ramach tej usługi. W tym przypadku tak nie jest. Polecenia SMTP użyte w przykładzie są następujące:
    • mail from: expéditeur, służące do podania adresu e-mail nadawcy wiadomości
    • rcpt to: destinataire, w celu podania adresu e-mail odbiorcy wiadomości. Jeśli jest wielu odbiorców, polecenie rcpt to: należy powtórzyć tyle razy, ile jest to konieczne, dla każdego z nich.
    • data, które sygnalizuje serwerowi SMTP, że zamierzamy wysłać wiadomość. Jak wskazano w odpowiedzi serwera, wiadomość ta jest ciągiem wierszy zakończonym wierszem zawierającym wyłącznie znak kropki. Wiadomość może zawierać nagłówki oddzielone od treści wiadomości pustym wierszem. W naszym przykładzie umieściliśmy temat za pomocą słowa kluczowego Subject:
  • po wysłaniu wiadomości można poinformować serwer o zakończeniu za pomocą polecenia quit. Serwer zamyka wówczas połączenie sieciowe. Wątek odczytu może wykryć to zdarzenie i zatrzymać się.
  • Następnie użytkownik wpisuje na klawiaturze „fin”, aby zatrzymać również wątek odczytu poleceń wpisywanych z klawiatury.

Jeśli sprawdzimy otrzymaną wiadomość, zobaczymy następujący zapis (Outlook):

Image

Należy zauważyć, że usługa SMTP nie jest w stanie wykryć, czy nadawca jest prawidłowy, czy nie. Dlatego nigdy nie można ufać polu from w wiadomości. W tym przypadku nadawca machin@univ-angers.fr nie istniał.

Ten ogólny klient TCP pozwala nam zidentyfikować protokół komunikacyjny usług internetowych, a na tej podstawie stworzyć wyspecjalizowane klasy dla klientów tych usług. Przyjrzyjmy się protokołowi komunikacyjnemu usługi POP (Post Office Protocol), który umożliwia odzyskiwanie wiadomości e-mail przechowywanych na serwerze. Działa on na porcie 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]

Główne polecenia są następujące:

  • user login, gdzie podaje się swoją nazwę użytkownika na serwerze przechowującym nasze wiadomości e-mail
  • pass password, gdzie podaje się hasło powiązane z poprzednim loginem
  • list – wyświetla listę wiadomości w postaci numeru i rozmiaru w bajtach
  • retr i – służy do odczytania wiadomości nr i
  • quit – aby zakończyć sesję.

Przyjrzyjmy się teraz protokołowi komunikacji między klientem a serwerem WWW, który zazwyczaj działa na porcie 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]

Klient internetowy wysyła swoje polecenia do serwera zgodnie z poniższym schematem:

commande1
commande2
...
commanden
[ligne vide]

Serwer WWW odpowiada dopiero po otrzymaniu pustego wiersza. W tym przykładzie użyliśmy tylko jednego polecenia:

GET /index.html HTTP/1.0

które żąda od serwera pliku URL /index.html i wskazuje, że korzysta z protokołu HTTP w wersji 1.0. Najnowsza wersja tego protokołu to 1.1. Przykład pokazuje, że serwer odpowiedział, przesyłając zawartość pliku index.html, a następnie zamknął połączenie, co widać po zakończeniu działania wątku odczytu odpowiedzi. Przed wysłaniem zawartości pliku index.html serwer WWW wysłał serię nagłówków zakończoną pustym wierszem:

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

Wiersz <html> jest pierwszym wierszem pliku /index.html. Powyższe elementy nazywane są nagłówkami HTTP (HyperText Transfer Protocol). Nie będziemy tutaj szczegółowo omawiać tych nagłówków, ale warto pamiętać, że nasz klient generyczny zapewnia do nich dostęp, co może być pomocne w ich zrozumieniu. Na przykład pierwszy wiersz:

<-- HTTP/1.1 200 OK

wskazuje, że serwer WWW, z którym nawiązano połączenie, obsługuje protokół HTTP/1.1 i że pomyślnie znalazł żądany plik (200 OK), przy czym 200 jest kodem odpowiedzi HTTP. Wiersze

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

informują klienta, że otrzyma 11251 bajtów reprezentujących tekst HTML (HyperText Markup Language) oraz że po zakończeniu wysyłania połączenie zostanie zamknięte.

Mamy więc do czynienia z bardzo praktycznym klientem TCP. Z pewnością oferuje on mniej funkcji niż program telnet, z którego korzystaliśmy wcześniej, ale warto było napisać go samodzielnie. Kod programu generycznego klienta TCP wygląda następująco:

// zaimportowane pakiety
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // przyjmuje jako parametr charakterystykę usługi w postaci
     // serwer port
     // łączy się z usługą
     // tworzy wątek do odczytu poleceń wpisywanych z klawiatury
     // polecenia te zostaną wysłane do serwera
     // tworzy wątek do odczytu odpowiedzi z serwera
     // odpowiedzi te zostaną wyświetlone na ekranie
     // całość kończy się poleceniem „fin” wpisanym na klawiaturze

   // zmienna instancji
  private static Socket client;

    public static void main(String[] args){

         // składnia
        final String syntaxe="pg serveur port";

         // liczba argumentów
        if(args.length != 2)
            erreur(syntaxe,1);

         // należy podać nazwę serwera
        String serveur=args[0];

         // port musi być liczbą całkowitą większą od 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;
         // mogą wystąpić problemy
        try{
             // nawiązywane jest połączenie z usługą
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // błąd
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // koniec
            return;
        }//przechwycenie

         // tworzy się wątki odczytu/zapisu
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // koniec wątku głównego
        return;
    }// main

     // wyświetlanie błędów
    public static void erreur(String msg, int exitCode){
         // wyświetlanie błędu
        System.err.println(msg);
         // zakończenie z błędem
        System.exit(exitCode);
    }//błąd
}//klasa  

class ClientSend extends Thread {
    // klasa odpowiedzialna za odczytywanie poleceń wpisywanych z klawiatury
     // oraz wysyłania ich na serwer za pośrednictwem klienta TCP przekazanego jako parametr

    private Socket client;    // klient TCP

     // konstruktor
    public ClientSend(Socket client){
         // odnotowujemy klienta TCP
        this.client=client;
    }//konstruktor

     // metoda Run wątku
    public void run(){

        // dane lokalne
        PrintWriter OUT=null;            // strumień zapisu sieciowego
    BufferedReader IN=null;        // strumień klawiatury
        String commande=null;            // polecenie odczytane z klawiatury

         // obsługa błędów
        try{
             // tworzenie strumienia zapisu sieciowego
            OUT=new PrintWriter(client.getOutputStream(),true);
      // tworzenie strumienia danych z klawiatury
      IN=new BufferedReader(new InputStreamReader(System.in));
            // pętla wprowadzania i wysyłania poleceń
            System.out.println("Commandes : ");
            while(true){
                 // odczyt polecenia wprowadzonego z klawiatury
                commande=IN.readLine().trim();
                // koniec?
                if (commande.toLowerCase().equals("fin")) break;
                // wysyłanie polecenia do serwera
                OUT.println(commande);
                 // następne polecenie
            }//while
        }catch(Exception ex){
             // błąd
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // koniec – zamykanie strumieni
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // zgłaszamy zakończenie wątku
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//klasa

class ClientReceive extends Thread{
    // klasa odpowiedzialna za odczytywanie wierszy tekstu przeznaczonych dla 
     // klienta TCP przekazanego jako parametr

    private Socket client;    // klient TCP

     // konstruktor
    public ClientReceive(Socket client){
         // odnotowujemy klienta TCP
        this.client=client;
    }//konstruktor

     // metoda Run wątku
    public void run(){

        // dane lokalne
        BufferedReader IN=null;        // strumień odczytu sieciowego
        String réponse=null;        // odpowiedź serwera

         // obsługa błędów
        try{
             // tworzenie strumienia odczytu sieciowego
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // pętla odczytu wierszy tekstowych ze strumienia IN
            while(true){
                 // odczyt strumienia sieciowego
                réponse=IN.readLine();
                 // czy strumień jest zamknięty?
                if(réponse==null) break;
                // wyświetlanie
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // błąd
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // koniec – zamykamy strumienie
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // zgłaszamy zakończenie wątku
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//klasa

9.2. Ogólny serwer TCP

Teraz zajmiemy się serwerem,

  • , który wyświetla na ekranie polecenia wysyłane przez klientów
  • i wysyła im w odpowiedzi linie tekstu wpisane przez użytkownika na klawiaturze. To właśnie użytkownik pełni rolę serwera.

Program uruchamia się poleceniem: java serveurTCPgenerique portEcoute, gdzie portEcoute to port, z którym klienci muszą się połączyć. Obsługa klienta będzie zapewniana przez dwa wątki:

  • wątek zajmujący się wyłącznie odczytywaniem wierszy tekstu wysyłanych przez klienta
  • wątek zajmujący się wyłącznie odczytywaniem odpowiedzi wpisywanych przez użytkownika na klawiaturze. Serwer zasygnalizuje za pomocą polecenia „fin”, że zamyka połączenie z klientem.

Serwer tworzy dwa wątki na każdego klienta. Jeśli jest n klientów, jednocześnie będzie aktywnych 2n wątków. Sam serwer nigdy się nie zatrzymuje, chyba że użytkownik naciśnie na klawiaturze klawisze Ctrl-C. Przyjrzyjmy się kilku przykładom.

Serwer uruchomiono na porcie 100 i do komunikacji z nim używamy klienta ogólnego. Okno klienta wygląda następująco:

E:\data\serge\MSNET\c#\sieć\klient tcp ogólny> 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]

Wiersze zaczynające się od <-- to te wysyłane z serwera do klienta, pozostałe to te wysyłane z klienta do serwera. Okno serwera wygląda następująco:


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]

Wiersze zaczynające się od <-- to te wysłane przez klienta do serwera. Wiersze oznaczone numerem N to te wysłane przez serwer do klienta nr N. Powyższy serwer nadal działa, podczas gdy klient 1 zakończył działanie. Uruchamiamy drugiego klienta dla tego samego serwera:


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]

Okno serwera wygląda wówczas następująco:


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

Teraz zasymulujmy serwer WWW, uruchamiając nasz serwer ogólnego przeznaczenia na porcie 88:


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

Otwórzmy teraz przeglądarkę i wpiszmy adres http://localhost:88/exemple.html. Przeglądarka połączy się wówczas z portem 88 na serwerze localhost, a następnie zażąda strony /exemple.html:

Image

Przyjrzyjmy się teraz oknu naszego serwera:

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

W ten sposób odkrywamy nagłówki HTTP wysyłane przez przeglądarkę. Pozwala nam to stopniowo poznawać protokół HTTP. W poprzednim przykładzie stworzyliśmy klienta internetowego, który wysyłał tylko jedno polecenie GET. To wystarczyło. Widzimy tutaj, że przeglądarka wysyła do serwera dodatkowe informacje. Ich celem jest wskazanie serwerowi, z jakim typem klienta ma do czynienia. Widzimy również, że nagłówki HTTP kończą się pustym wierszem.

Przygotujmy odpowiedź dla naszego klienta. Użytkownik korzystający z klawiatury jest tutaj prawdziwym serwerem i może ręcznie przygotować odpowiedź. Przypomnijmy sobie odpowiedź wysłaną przez serwer WWW w poprzednim przykładzie:

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

Spróbujmy udzielić podobnej odpowiedzi:

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

Wiersze zaczynające się od 2: są wysyłane z serwera do klienta nr 2. Polecenie fin zamyka połączenie serwera z klientem. W naszej odpowiedzi ograniczyliśmy się do następujących nagłówków HTTP:

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

Nie podajemy rozmiaru pliku, który zamierzamy wysłać (Content-Length), a jedynie informujemy, że po jego wysłaniu zamkniemy połączenie (Connection: close). To wystarczy dla przeglądarki. Gdy przeglądarka zauważy, że połączenie zostało zamknięte, uzna, że odpowiedź serwera została zakończona i wyświetli stronę HTML, która została jej przesłana. Wygląda ona następująco:

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>

Następnie użytkownik zamyka połączenie z klientem, wpisując polecenie fin. Przeglądarka rozpoznaje wówczas, że odpowiedź serwera została zakończona i może ją wyświetlić:

Image

Jeśli w powyższym przykładzie wpiszemy View/Source, aby sprawdzić, co otrzymała przeglądarka, otrzymamy:

Image

czyli dokładnie to, co wysłano z serwera generycznego.

Kod serwera TCP o charakterze ogólnym wygląda następująco:

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

public class serveurTCPgenerique{

    // program główny
    public static void main (String[] args){

    // odbiera żądania klientów na porcie nasłuchowym
     // tworzy wątek do odczytu żądań klienta
     // zostaną one wyświetlone na ekranie
     // tworzy wątek do odczytu poleceń wpisywanych z klawiatury
     // polecenia te zostaną wysłane jako odpowiedź do klienta
     // całość kończy się poleceniem fin wpisanym z klawiatury

    final String syntaxe="Syntaxe : pg port";
   // zmienna instancji
         // czy istnieje argument
     if(args.length != 1)
        erreur(syntaxe,1);

         // port musi być liczbą całkowitą większą od 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);

     // tworzymy serwis nasłuchujący
    ServerSocket ecoute=null;
    int nbClients=0;    // liczba obsłużonych klientów
        try{
             // tworzymy usługę
            ecoute=new ServerSocket(port);
             // monitorowanie
            System.out.println("Serveur générique lancé sur le port " + port);

             // pętla obsługi klientów
            Socket client=null;
            while (true){ // pętla nieskończona  zostanie zatrzymana przez Ctrl-C
                 // oczekiwanie na klienta
                client=ecoute.accept();

                 // usługa jest realizowana przez oddzielne wątki
                nbClients++;

                 // tworzenie wątków odczytu/zapisu
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // powrót do nasłuchiwania żądań
            }// koniec pętli while
        }catch(Exception ex){
             // zgłaszamy błąd
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// koniec funkcji main

     // wyświetlanie błędów
    public static void erreur(String msg, int exitCode){
         // wyświetlanie błędu
        System.err.println(msg);
         // zatrzymanie z błędem
        System.exit(exitCode);
    }//błąd
}//klasa

class ServeurSend extends Thread{
    // klasa odpowiedzialna za odczytywanie odpowiedzi wpisywanych z klawiatury
     // i wysyłania ich do klienta za pośrednictwem klienta TCP przekazanego do konstruktora

    Socket client;    // klient TCP
    int numClient;        // numer klienta

     // konstruktor
    public ServeurSend(Socket client, int numClient){
         // odnotowujemy klienta TCP
        this.client=client;
         // oraz jego numer
        this.numClient=numClient;
    }//producent

     // metoda Run wątku
    public void run(){

        // dane lokalne
        PrintWriter OUT=null;        // strumień zapisu sieciowego
        String réponse=null;        // odpowiedź odczytana z klawiatury
    BufferedReader IN=null;    // strumień klawiatury

        // monitorowanie
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // obsługa błędów
        try{
             // tworzenie strumienia zapisu sieciowego
            OUT=new PrintWriter(client.getOutputStream(),true);
      // tworzenie strumienia klawiatury
      IN=new BufferedReader(new InputStreamReader(System.in));
            // pętla wprowadzania i wysyłania poleceń
            while(true){
                 // identyfikacja klienta
                System.out.print("--> " + numClient + " : ");
                 // odczyt odpowiedzi wpisanej z klawiatury
                réponse=IN.readLine().trim();
                // koniec?
                if (réponse.toLowerCase().equals("fin")) break;
                // wysłanie odpowiedzi na serwer
                OUT.println(réponse);
                 // następna odpowiedź
            }//while
        }catch(Exception ex){
             // błąd
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // koniec  zamykanie strumieni
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // zgłaszamy zakończenie wątku
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//run
}//klasa

class ServeurReceive extends Thread{
    // klasa odpowiedzialna za odczytywanie wierszy tekstu wysyłanych do serwera 
     // za pośrednictwem klienta TCP przekazanego do konstruktora

    Socket client;    // klient TCP
    int numClient;        // numer klienta

     // konstruktor
    public ServeurReceive(Socket client, int numClient){
         // odnotowujemy klienta TCP
        this.client=client;
         // oraz jego numer
        this.numClient=numClient;
    }//producent

     // metoda Run wątku
    public void run(){

        // dane lokalne
        BufferedReader IN=null;        // strumień odczytu sieciowego
        String réponse=null;        // odpowiedź serwera

         // monitorowanie
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // obsługa błędów
        try{
             // tworzenie strumienia odczytu sieciowego
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // pętla odczytu wierszy tekstu ze strumienia IN
            while(true){
                 // odczyt strumienia sieciowego
                réponse=IN.readLine();
                 // czy strumień jest zamknięty?
                if(réponse==null) break;
                // wyświetlanie
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // błąd
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // koniec  zamykamy strumienie
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // zgłaszamy zakończenie wątku
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//klasa