9. Вихідний код програм
9.1. Універсальний клієнт TCP
Багато сервісів, створених на початку розвитку Інтернету, працюють за моделлю сервера-відлуння, який ми розглядали раніше: обмін даними між клієнтом і сервером відбувається шляхом обміну рядками тексту. Ми напишемо універсальний TCP-клієнт, який запускатиметься таким чином: java cltTCPgenerique сервер порт
Цей клієнт TCP підключиться до порту port сервера serveur. Після цього він створить два потоки:
- потік, відповідальний за зчитування команд, введених з клавіатури, та їх надсилання на сервер
- потік, відповідальний за зчитування відповідей сервера та їх відображення на екрані
Чому два потоки? Кожна служба TCP-IP має свій особливий протокол, і іноді трапляються такі ситуації:
- клієнт повинен надіслати кілька рядків тексту, перш ніж отримати відповідь
- відповідь сервера може містити кілька рядків тексту
Тому цикл «відправлення одного рядка на сервер — отримання одного рядка від сервера» не завжди підходить. Тому ми створимо два окремі цикли:
- цикл зчитування команд, введених з клавіатури для відправки на сервер. Користувач позначатиме кінець команд ключовим словом fin.
- цикл прийому та відображення відповідей від сервера. Це буде нескінченний цикл, який перериватиметься лише у разі закриття мережевого каналу сервером або користувачем за допомогою клавіатури, який введе команду fin.
Щоб ці дві петлі працювали незалежно одна від одної, нам потрібні два незалежні потоки. Розглянемо приклад виконання, в якому наш загальний клієнт TCP підключається до сервісу SMTP (SendMail Transfer Protocol). Цей сервіс відповідає за доставку електронної пошти адресатам. Він працює на порту 25 і використовує протокол взаємодії типу обміну текстовими рядками.
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]
Проаналізуємо ці обміни даними між клієнтом і сервером:
- сервіс SMTP надсилає привітальне повідомлення, коли клієнт підключається до нього:
- деякі сервіси мають команду help, яка надає інформацію про команди, що можуть використовуватися з сервісом. У цьому випадку це не так. Команди SMTP, що використовуються в прикладі, такі:
- mail from: expéditeur — для вказівки електронної адреси відправника повідомлення
- rcpt to: destinataire — для вказівки електронної адреси одержувача повідомлення. Якщо одержувачів декілька, команду rcpt to: повторюють стільки разів, скільки потрібно, для кожного з них.
- data, що повідомляє серверу SMTP про те, що ми збираємося надіслати повідомлення. Як зазначено у відповіді сервера, це послідовність рядків, що закінчується рядком, який містить лише один символ — крапку. Повідомлення може мати заголовки, відокремлені від тіла повідомлення порожнім рядком. У нашому прикладі ми вказали тему за допомогою ключового слова Subject:
- після відправлення повідомлення можна повідомити серверу про завершення роботи за допомогою команди quit. Після цього сервер закриває мережеве з’єднання. Потік читання може виявити цю подію та зупинитися.
- Потім користувач вводить «fin» на клавіатурі, щоб також зупинити потік читання команд, введених з клавіатури.
Якщо перевірити отриману пошту, ми побачимо таке (Outlook):

Слід зауважити, що служба SMTP не може визначити, чи є відправник дійсним. Тому ніколи не можна довіряти полю from у повідомленні. У цьому випадку відправник machin@univ-angers.fr не існував.
Цей загальний TCP-клієнт дозволяє нам виявити протокол взаємодії інтернет-сервісів і на цій основі створити спеціалізовані класи для клієнтів цих сервісів. Давайте визначимо протокол взаємодії сервісу POP (Post Office Protocol), який дозволяє отримувати електронні листи, що зберігаються на сервері. Він працює на порту 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]
Основні команди такі:
- user login, де вказується логін на сервері, на якому зберігаються наші електронні листи
- pass password, де вказується пароль, пов'язаний із попереднім логіном
- list — для отримання списку повідомлень у форматі «номер, розмір у байтах»
- retr i — для читання повідомлення № i
- quit — для завершення діалогу.
Тепер розглянемо протокол взаємодії між клієнтом і веб-сервером, який зазвичай працює на порту 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]
Веб-клієнт надсилає свої команди на сервер за такою схемою:
Лише після отримання порожнього рядка веб-сервер надає відповідь. У цьому прикладі ми використали лише одну команду:
яка запитує у сервера URL /index.html та вказує, що працює з протоколом HTTP версії 1.0. Найновіша версія цього протоколу — 1.1. Приклад показує, що сервер відповів, надіславши вміст файлу index.html, а потім закрив з’єднання, оскільки видно, що потік читання відповідей завершився. Перед надсиланням вмісту файлу index.html веб-сервер надіслав серію заголовків, що закінчувалася порожнім рядком:
<-- 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>
Рядок <html> є першим рядком файлу /index.html. Вищезазначене називається заголовками HTTP (HyperText Transfer Protocol). Ми не будемо тут детально розглядати ці заголовки, але нагадаємо, що наш універсальний клієнт надає до них доступ, що може бути корисним для їх розуміння. Наприклад, перший рядок:
вказує, що веб-сервер, до якого звернулися, підтримує протокол HTTP/1.1 і що він успішно знайшов запитуваний файл (200 OK), де 200 — це код відповіді HTTP. Рядки
повідомляють клієнту, що він отримає 11251 байт, які представляють текст HTML (HyperText Markup Language), і що після завершення передачі з'єднання буде закрито.
Отже, ми маємо дуже зручний TCP-клієнт. Він, безперечно, робить менше, ніж програма telnet, яку ми використовували раніше, але було цікаво написати його самостійно. Програма загального TCP-клієнта виглядає так:
// імпортовані пакети
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
// отримує як параметр характеристики служби у вигляді
// сервер, порт
// підключається до служби
// створює потік для зчитування команд, введених з клавіатури
// ці команди будуть надіслані на сервер
// створює потік для зчитування відповідей від сервера
// вони будуть відображатися на екрані
// усе завершується командою «fin», введеною з клавіатури
// змінна екземпляру
private static Socket client;
public static void main(String[] args){
// синтаксис
final String syntaxe="pg serveur port";
// кількість аргументів
if(args.length != 2)
erreur(syntaxe,1);
// записуємо ім’я сервера
String serveur=args[0];
// порт повинен бути цілим числом >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;
// можуть виникнути проблеми
try{
// здійснюється підключення до служби
client=new Socket(serveur,port);
}catch(Exception ex){
// помилка
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
// кінець
return;
}//catch
// створюються потоки читання/запису
new ClientSend(client).start();
new ClientReceive(client).start();
// завершення головного потоку
return;
}// main
// виведення повідомлень про помилки
public static void erreur(String msg, int exitCode){
// виведення помилки
System.err.println(msg);
// зупинка з помилкою
System.exit(exitCode);
}//помилка
}//клас
class ClientSend extends Thread {
// клас, відповідальний за зчитування команд, введених з клавіатури
// та надсилати їх на сервер через TCP-клієнт, переданий як параметр
private Socket client; // TCP-клієнт
// конструктор
public ClientSend(Socket client){
// зазначаємо TCP-клієнт
this.client=client;
}//конструктор
// метод Run потоку
public void run(){
// локальні дані
PrintWriter OUT=null; // потік запису в мережу
BufferedReader IN=null; // потік клавіатури
String commande=null; // команда, зчитана з клавіатури
// обробка помилок
try{
// створення потоку запису в мережу
OUT=new PrintWriter(client.getOutputStream(),true);
// створення потоку введення з клавіатури
IN=new BufferedReader(new InputStreamReader(System.in));
// цикл введення-відправлення команд
System.out.println("Commandes : ");
while(true){
// зчитування команди, введеної з клавіатури
commande=IN.readLine().trim();
// завершено?
if (commande.toLowerCase().equals("fin")) break;
// відправлення команди на сервер
OUT.println(commande);
// наступна команда
}//while
}catch(Exception ex){
// помилка
System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// кінець — закриваємо потоки
try{
OUT.close();client.close();
}catch(Exception ex){}
// повідомляємо про завершення потоку
System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//run
}//клас
class ClientReceive extends Thread{
// клас, відповідальний за зчитування рядків тексту, призначених для
// TCP-клієнта, переданого як параметр
private Socket client; // TCP-клієнт
// конструктор
public ClientReceive(Socket client){
// записуємо TCP-клієнта
this.client=client;
}//конструктор
// метод Run потоку
public void run(){
// локальні дані
BufferedReader IN=null; // потік читання мережі
String réponse=null; // відповідь сервера
// обробка помилок
try{
// створення потоку читання з мережі
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// цикл читання рядків тексту з потоку IN
while(true){
// читання мережевого потоку
réponse=IN.readLine();
// потік закритий?
if(réponse==null) break;
// виведення на екран
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// помилка
System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// кінець — закриваємо потоки
try{
IN.close();client.close();
}catch(Exception ex){}
// повідомляємо про завершення потоку
System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//run
}//клас
9.2. Загальний TCP-сервер
Тепер ми розглянемо сервер,
- , який виводить на екран команди, надіслані його клієнтами
- та надсилає їм у відповідь рядки тексту, введені користувачем з клавіатури. Отже, саме користувач виконує роль сервера.
Програму запускають за допомогою команди: java serveurTCPgenerique portEcoute, де portEcoute — це порт, до якого повинні підключатися клієнти. Обслуговування клієнтів забезпечуватимуть два потоки:
- потік, який присвячений виключно зчитуванню рядків тексту, надісланих клієнтом
- потік, який присвячений виключно зчитуванню відповідей, введених користувачем з клавіатури. Цей потік за допомогою команди fin повідомить про завершення з’єднання з клієнтом.
Сервер створює два потоки на кожного клієнта. Якщо є n клієнтів, одночасно буде активних 2n потоків. Сам сервер ніколи не зупиняється, за винятком випадків, коли користувач натискає комбінацію клавіш Ctrl-C. Розглянемо кілька прикладів.
Сервер запущено на порту 100, а для зв’язку з ним використовується універсальний клієнт. Вікно клієнта виглядає так:
E:\data\serge\MSNET\c#\мережа\клієнт tcp загальний> 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]
Рядки, що починаються з <--, — це дані, надіслані сервером клієнту, інші — дані, надіслані клієнтом серверу. Вікно сервера виглядає так:
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]
Рядки, що починаються з <--, — це ті, що надсилаються клієнтом на сервер. Рядки N: — це рядки, що надсилаються сервером клієнту № N. Вищезазначений сервер все ще активний, тоді як клієнт 1 завершив роботу. Запускаємо другого клієнта для того самого сервера:
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]
Вікно сервера виглядає так:
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
Тепер змоделюємо веб-сервер, запустивши наш універсальний сервер на порту 88:
Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Тепер відкриємо браузер і введемо адресу http://localhost:88/exemple.html. Браузер підключиться до порту 88 машини localhost, а потім завантажить сторінку /exemple.html:

Тепер поглянемо на вікно нашого сервера:
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
<--
Таким чином ми виявляємо заголовки HTTP, надіслані браузером. Це дозволяє нам поступово розібратися з протоколом HTTP. У попередньому прикладі ми створили веб-клієнт, який надсилав лише одну команду GET. Цього було достатньо. Тут ми бачимо, що браузер надсилає серверу й іншу інформацію. Її мета — повідомити серверу, з яким типом клієнта він має справу. Ми також бачимо, що заголовки HTTP закінчуються порожнім рядком.
Складімо відповідь для нашого клієнта. Користувач за клавіатурою тут є справжнім сервером і може скласти відповідь вручну. Згадаймо відповідь, яку надав веб-сервер у попередньому прикладі:
<-- 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>
Спробуємо дати аналогічну відповідь:
...
<-- 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]
Рядки, що починаються з 2: надсилаються сервером клієнту № 2. Команда fin завершує з’єднання сервера з клієнтом. У нашій відповіді ми обмежилися такими заголовками HTTP:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Ми не вказуємо розмір файлу, який збираємося надіслати (Content-Length), а лише повідомляємо, що після його надсилання ми закриємо з’єднання (Connection: close). Цього достатньо для браузера. Побачивши, що з’єднання закрито, браузер зрозуміє, що відповідь сервера завершена, і відобразить сторінку HTML, яку йому надіслали. Остання має такий вигляд:
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>
Потім користувач закриває з'єднання з клієнтом, ввівши команду fin. Після цього браузер розуміє, що відповідь сервера завершена, і може її відобразити:

Якщо після цього виконати команду View/Source, щоб переглянути, що отримав браузер, отримаємо:

тобто саме те, що було надіслано з загального сервера.
Код загального TCP-сервера такий:
// пакети
import java.io.*;
import java.net.*;
public class serveurTCPgenerique{
// головна програма
public static void main (String[] args){
// приймає запити клієнтів на порту прослуховування
// створює потік для зчитування запитів клієнта
// ці запити будуть відображатися на екрані
// створює потік для зчитування команд, введених з клавіатури
// вони будуть надіслані клієнту у відповідь
// усе завершується командою «fin», введеною з клавіатури
final String syntaxe="Syntaxe : pg port";
// змінна екземпляру
// чи є аргумент
if(args.length != 1)
erreur(syntaxe,1);
// порт повинен бути цілим числом >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);
// створюємо службу прослуховування
ServerSocket ecoute=null;
int nbClients=0; // кількість оброблених клієнтів
try{
// створюється служба
ecoute=new ServerSocket(port);
// відстеження
System.out.println("Serveur générique lancé sur le port " + port);
// цикл обслуговування клієнтів
Socket client=null;
while (true){ // нескінченний цикл — зупиняється за допомогою Ctrl-C
// очікування клієнта
client=ecoute.accept();
// сервіс виконується окремими потоками
nbClients++;
// створюються потоки читання/запису
new ServeurSend(client,nbClients).start();
new ServeurReceive(client,nbClients).start();
// повернення до очікування запитів
}// кінець циклу while
}catch(Exception ex){
// повідомляється про помилку
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// кінець main
// виведення повідомлень про помилки
public static void erreur(String msg, int exitCode){
// виведення помилки
System.err.println(msg);
// зупинка з помилкою
System.exit(exitCode);
}//помилка
}//клас
class ServeurSend extends Thread{
// клас, відповідальний за зчитування відповідей, введених з клавіатури
// та надсилання їх клієнту через TCP-клієнт, переданий у конструктор
Socket client; // TCP-клієнт
int numClient; // номер клієнта
// конструктор
public ServeurSend(Socket client, int numClient){
// запишемо TCP-клієнт
this.client=client;
// та його номер
this.numClient=numClient;
}//виробник
// метод Run потоку
public void run(){
// локальні дані
PrintWriter OUT=null; // потік запису в мережу
String réponse=null; // відповідь, зчитана з клавіатури
BufferedReader IN=null; // потік клавіатури
// відстеження
System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// обробка помилок
try{
// створення потоку запису в мережі
OUT=new PrintWriter(client.getOutputStream(),true);
// створення потоку клавіатури
IN=new BufferedReader(new InputStreamReader(System.in));
// цикл введення-відправлення команд
while(true){
// ідентифікація клієнта
System.out.print("--> " + numClient + " : ");
// читання відповіді, введеної з клавіатури
réponse=IN.readLine().trim();
// закінчено?
if (réponse.toLowerCase().equals("fin")) break;
// відправлення відповіді на сервер
OUT.println(réponse);
// наступна відповідь
}//while
}catch(Exception ex){
// помилка
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// кінець — закриття потоків
try{
OUT.close();client.close();
}catch(Exception ex){}
// повідомляємо про завершення потоку
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//run
}//клас
class ServeurReceive extends Thread{
// клас, відповідальний за зчитування рядків тексту, надісланих на сервер
// через TCP-клієнт, переданий конструктору
Socket client; // TCP-клієнт
int numClient; // номер клієнта
// конструктор
public ServeurReceive(Socket client, int numClient){
// відзначаємо TCP-клієнт
this.client=client;
// та його номер
this.numClient=numClient;
}//виробник
// метод Run потоку
public void run(){
// локальні дані
BufferedReader IN=null; // потік читання мережі
String réponse=null; // відповідь сервера
// відстеження
System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// обробка помилок
try{
// створення потоку читання мережі
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// цикл читання рядків тексту з потоку IN
while(true){
// читання мережевого потоку
réponse=IN.readLine();
// потік закритий?
if(réponse==null) break;
// виведення на екран
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// помилка
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// кінець — закриваємо потоки
try{
IN.close();client.close();
}catch(Exception ex){}
// повідомляємо про завершення потоку
System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//run
}//клас