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:
- wątek odpowiedzialny za odczytywanie poleceń wpisywanych z klawiatury i wysyłanie ich do serwera
- 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:
- 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):

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:
Serwer WWW odpowiada dopiero po otrzymaniu pustego wiersza. W tym przykładzie użyliśmy tylko jednego polecenia:
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:
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
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:

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

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

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