9. كود مصدر البرامج
9.1. العميل العام TCP
تعمل العديد من الخدمات التي تم إنشاؤها في بدايات الإنترنت وفقًا لنموذج خادم الصدى الذي درسناه سابقًا: حيث تتم عمليات التبادل بين العميل والخادم عن طريق تبادل أسطر نصية. سنقوم بكتابة عميل TCP عام يتم تشغيله بالطريقة التالية: java cltTCPgenerique خادم المنفذ
سيتصل هذا العميل TCP بالمنفذ port الخاص بالخادم serveur. وبمجرد القيام بذلك، سيقوم بإنشاء مؤشرين:
- خيط مسؤول عن قراءة الأوامر المكتوبة على لوحة المفاتيح وإرسالها إلى الخادم
- خيط آخر مسؤول عن قراءة الردود الواردة من الخادم وعرضها على الشاشة
لماذا خيطان؟ كل خدمة TCP-IP لها بروتوكولها الخاص، وقد نواجه أحيانًا الحالات التالية:
- يجب على العميل إرسال عدة أسطر من النص قبل الحصول على رد
- قد تتضمن استجابة الخادم عدة أسطر من النص
لذلك، فإن الحلقة التي تتمثل في إرسال سطر واحد إلى الخادم - واستلام سطر واحد مرسَل من الخادم - لا تكون مناسبة دائمًا. لذا سنقوم بإنشاء حلقتين منفصلتين:
- حلقة لقراءة الأوامر المكتوبة على لوحة المفاتيح لإرسالها إلى الخادم. سيشير المستخدم إلى نهاية الأوامر باستخدام الكلمة الرئيسية fin.
- حلقة لاستقبال وعرض ردود الخادم. وستكون هذه الحلقة حلقة لا نهائية لا تتوقف إلا بإغلاق تدفق الشبكة من قِبل الخادم أو من قِبل المستخدم عبر لوحة المفاتيح عند كتابة الأمر fin.
للحصول على هاتين الحلقتين المنفصلتين، نحتاج إلى خيطين مستقلين. لنعرض مثالاً على التنفيذ حيث يتصل عميل TCP العام لدينا بخدمة SMTP (بروتوكول النقل SendMail). هذه الخدمة مسؤولة عن توجيه البريد الإلكتروني إلى المستلمين. وهي تعمل على المنفذ 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 بأننا سنرسل الرسالة. كما هو موضح في رد الخادم، فإن هذه الأوامر عبارة عن سلسلة من الأسطر تنتهي بسطر يحتوي على حرف النقطة فقط. قد تحتوي الرسالة على رؤوس (headers) مفصولة عن نص الرسالة بسطر فارغ. في مثالنا، قمنا بتحديد موضوع باستخدام الكلمة المفتاحية Subject:
- بمجرد إرسال الرسالة، يمكن إعلام الخادم بأننا انتهينا باستخدام الأمر quit. عندئذٍ يقوم الخادم بإغلاق اتصال الشبكة. ويمكن لسلسلة عمليات القراءة اكتشاف هذا الحدث والتوقف.
- ثم يضغط المستخدم على مفتاح «fin» على لوحة المفاتيح لإيقاف مؤشر القراءة الخاص بالأوامر المكتوبة على لوحة المفاتيح أيضًا.
إذا تحققنا من البريد الوارد، فسنجد ما يلي (Outlook):

يُلاحظ أن الخدمة SMTP لا يمكنها تحديد ما إذا كان المرسل صالحًا أم لا. ولذلك لا يمكن أبدًا الوثوق بحقل from في أي رسالة. في هذه الحالة، لم يكن المرسل machin@univ-angers.fr موجودًا.
يمكن لهذا العميل TCP العام أن يتيح لنا اكتشاف بروتوكول الاتصال الخاص بخدمات الإنترنت، ومن ثم إنشاء فئات متخصصة لعملاء هذه الخدمات. دعونا نكتشف بروتوكول الاتصال الخاص بالخدمة POP (بروتوكول مكتب البريد) الذي يتيح استرداد رسائل البريد الإلكتروني المخزنة على الخادم. وهو يعمل على المنفذ 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). لن ندخل هنا في تفاصيل هذه الرؤوس، لكن تجدر الإشارة إلى أن عميلنا العام يتيح الوصول إليها، وهو ما قد يكون مفيدًا لفهمها. على سبيل المثال، السطر الأول:
تشير إلى أن خادم الويب الذي تم الاتصال به يدعم بروتوكول HTTP/1.1 وأنه عثر بالفعل على الملف المطلوب (200 OK)، حيث يمثل الرقم 200 رمز استجابة HTTP. الأسطر
تُعلم العميل بأنه سيتلقى 11251 بايت تمثل النص HTML (لغة ترميز HyperText) وأنه عند انتهاء الإرسال، سيتم إغلاق الاتصال.
إذن، لدينا هنا عميل 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;
}//استثناء
// يتم إنشاء مؤشرات ترابط القراءة/الكتابة
new ClientSend(client).start();
new ClientReceive(client).start();
// نهاية مؤشر الترابط الرئيسي
return;
}// الرئيسي
// عرض الأخطاء
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]");
}//تشغيل
}//فئة
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]");
}//تشغيل
}//فئة
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;
}//الشركة المصنعة
// طريقة تشغيل الخيط
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());
}//استثناء
// النهاية - إغلاق التدفقات
try{
OUT.close();client.close();
}catch(Exception ex){}
// الإبلاغ عن انتهاء مؤشر الترابط
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//التشغيل
}//فئة
class ServeurReceive extends Thread{
// فئة مكلفة بقراءة أسطر النص المرسلة إلى الخادم
// عبر عميل TCP تم تمريره إلى المنشئ
Socket client; // عميل TCP
int numClient; // رقم العميل
// المنشئ
public ServeurReceive(Socket client, int numClient){
// يُلاحظ عميل TCP
this.client=client;
// ورقمه
this.numClient=numClient;
}//الشركة المصنعة
// طريقة تشغيل الخيط
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+"]");
}//تشغيل
}//فئة