8. برمجة TCP-IP
8.1. معلومات عامة
8.1.1. بروتوكولات الإنترنت
نقدم هنا مقدمة لبروتوكولات الاتصال عبر الإنترنت، والمعروفة أيضًا باسم مجموعة TCP/IP (بروتوكول التحكم في الإرسال / بروتوكول الإنترنت)، والتي سُميت على اسم البروتوكولين الرئيسيين. يُنصح القارئ بأن يكون لديه فهم عام لكيفية عمل الشبكات، ولا سيما بروتوكولات TCP/IP، قبل الشروع في تطوير التطبيقات الموزعة.
النص التالي هو ترجمة جزئية لمقطع موجود في "LAN Workplace for DOS – Administrator’s Guide" (دليل المسؤول عن LAN Workplace لنظام DOS) الصادر عن شركة NOVELL، وهو وثيقة تعود إلى أوائل التسعينيات.
ينبع المفهوم العام لإنشاء شبكة من أجهزة الكمبيوتر غير المتجانسة من الأبحاث التي أجرتها وكالة DARPA (وكالة مشاريع الأبحاث المتقدمة للدفاع) في الولايات المتحدة. طورت DARPA مجموعة البروتوكولات المعروفة باسم TCP/IP، والتي تسمح للأجهزة غير المتجانسة بالتواصل مع بعضها البعض. تم اختبار هذه البروتوكولات على شبكة تسمى ARPAnet، والتي أصبحت فيما بعد الإنترنت. تحدد بروتوكولات TCP/IP تنسيقات وقواعد الإرسال والاستقبال التي تكون مستقلة عن بنية الشبكة والأجهزة المستخدمة.
الشبكة التي صممتها DARPA وتديرها بروتوكولات TCP/IP هي شبكة مبدلة بالحزم. تنقل هذه الشبكة المعلومات عبر الشبكة في أجزاء صغيرة تسمى الحزم. وبالتالي، إذا أرسل جهاز كمبيوتر ملفًا كبيرًا، فسيتم تقسيمه إلى أجزاء صغيرة يتم إرسالها عبر الشبكة ليتم إعادة تجميعها في الوجهة. يحدد TCP/IP تنسيق هذه الحزم، وهي:
- مصدر الحزمة
- الوجهة
- الطول
- النوع
8.1.2. نموذج OSI
تتبع بروتوكولات TCP/IP بشكل عام نموذج الشبكة المفتوحة المعروف باسم OSI (نموذج مرجعي لترابط الأنظمة المفتوحة) الذي حددته منظمة ISO (منظمة المعايير الدولية). يصف هذا النموذج شبكة مثالية حيث يمكن تمثيل الاتصال بين الأجهزة بنموذج مكون من سبع طبقات:

تتلقى كل طبقة خدمات من الطبقة التي تحتها وتقدم خدماتها الخاصة إلى الطبقة التي فوقها. لنفترض أن تطبيقين موجودين على جهازين مختلفين A و B يريدان التواصل: فهما يفعلان ذلك في طبقة التطبيقات. ولا يحتاجان إلى معرفة كل تفاصيل كيفية عمل الشبكة: فكل تطبيق يمرر المعلومات التي يرغب في إرسالها إلى الطبقة التي تحته: طبقة العرض. وبالتالي، لا يحتاج التطبيق سوى إلى معرفة قواعد التفاعل مع طبقة العرض.
بمجرد وصول المعلومات إلى طبقة العرض، يتم تمريرها وفقًا لقواعد أخرى إلى طبقة الجلسة، وهكذا دواليك، حتى تصل المعلومات إلى الوسيط المادي ويتم إرسالها فعليًا إلى الجهاز الوجهة. وهناك، ستخضع لعملية عكسية لما خضعت له على الجهاز المرسل.
في كل طبقة، تقوم عملية الإرسال المسؤولة عن إرسال المعلومات بإرسالها إلى عملية استقبال على الجهاز الآخر الذي ينتمي إلى نفس الطبقة التي تنتمي إليها. وتقوم بذلك وفقًا لقواعد معينة تُعرف باسم بروتوكول الطبقة. وبالتالي، نحصل على مخطط الاتصال النهائي التالي:

أدوار الطبقات المختلفة هي كما يلي:
تضمن نقل البتات عبر وسيط مادي. تشمل هذه الطبقة معدات طرفية لمعالجة البيانات (DPTE) مثل المحطات الطرفية أو أجهزة الكمبيوتر، بالإضافة إلى معدات إنهاء دوائر البيانات (DCTE) مثل أجهزة التضمين/التفكيك، وأجهزة التعدد، والمركّزات. النقاط الرئيسية في هذا المستوى هي: . اختيار ترميز المعلومات (تناظري أو رقمي) . اختيار وضع الإرسال (متزامن أو غير متزامن). | |
يخفي الخصائص المادية للطبقة المادية. يكتشف أخطاء الإرسال ويصححها. | |
يدير المسار الذي يجب أن تسلكه المعلومات المرسلة عبر الشبكة. ويُطلق على ذلك اسم التوجيه: أي تحديد المسار الذي يجب أن تسلكه المعلومات للوصول إلى وجهتها. | |
يتيح الاتصال بين تطبيقين، في حين أن الطبقات السابقة كانت تسمح فقط بالاتصال بين الأجهزة. يمكن أن تكون إحدى الخدمات التي توفرها هذه الطبقة هي تعدد الإرسال: يمكن لطبقة النقل استخدام اتصال شبكة واحد (من جهاز إلى جهاز) لنقل البيانات الخاصة بتطبيقات متعددة. | |
توفر هذه الطبقة خدمات تسمح للتطبيق بفتح جلسة عمل والحفاظ عليها على جهاز بعيد. | |
تهدف إلى توحيد تمثيل البيانات عبر الأجهزة المختلفة. وبالتالي، سيتم "تنسيق" البيانات الصادرة من الجهاز A بواسطة طبقة العرض الخاصة بالجهاز A وفقًا لتنسيق قياسي قبل إرسالها عبر الشبكة. عند وصولها إلى طبقة العرض الخاصة بالجهاز B الوجهة، والذي سيتعرف عليها بفضل تنسيقها القياسي، سيتم تنسيقها بشكل مختلف حتى يتمكن التطبيق الموجود على الجهاز B من التعرف عليها. | |
في هذا المستوى، نجد التطبيقات التي تكون عمومًا قريبة من المستخدم، مثل البريد الإلكتروني أو نقل الملفات. |
8.1.3. نموذج TCP/IP
نموذج OSI هو نموذج مثالي لم يتم تحقيقه بالكامل قط. تقترب مجموعة بروتوكولات TCP/IP منه بالطريقة التالية:

الطبقة المادية
في الشبكات المحلية، تُستخدم تقنية إيثرنت أو توكن رينغ بشكل عام. سنركز هنا على تقنية إيثرنت فقط.
إيثرنت
هذا هو الاسم الذي أُطلق على تقنية الشبكة المحلية ذات التبديل الحزمي التي اخترعت في مركز أبحاث زيروكس (Xerox PARC) في أوائل السبعينيات، وقامت شركات زيروكس وإنتل وديجيتال إيكويبمنت بتوحيد معاييرها في عام 1978. تتكون الشبكة ماديًا من كابل متحد المحور يبلغ قطره حوالي 1.27 سم وطوله يصل إلى 500 متر. ويمكن تمديدها باستخدام مكررات، على ألا يفصل بين أي جهازين أكثر من مكررين. الكابل سلبي: حيث توجد جميع المكونات النشطة على الأجهزة المتصلة بالكابل. ويتم توصيل كل جهاز بالكابل عبر بطاقة وصول إلى الشبكة تتكون من:
- جهاز إرسال واستقبال يكتشف وجود الإشارات على الكابل ويحول الإشارات التناظرية إلى إشارات رقمية والعكس صحيح.
- مقرن يستقبل الإشارات الرقمية من جهاز الإرسال والاستقبال وينقلها إلى الكمبيوتر للمعالجة، أو العكس.
الميزات الرئيسية لتقنية إيثرنت هي كما يلي:
- سعة 10 ميجابت في الثانية.
- توبولوجيا الناقل: جميع الأجهزة متصلة بنفس الكابل

- شبكة البث — ترسل الجهاز المرسل المعلومات عبر الكابل مع عنوان الجهاز المستقبل. ثم تتلقى جميع الأجهزة المتصلة هذه المعلومات، ولا يحتفظ بها سوى المستلم المقصود.
- طريقة الوصول هي كما يلي: يستمع جهاز الإرسال الراغب في الإرسال إلى الكابل — ثم يكتشف ما إذا كانت موجة حاملة موجودة أم لا، حيث يشير وجودها إلى أن عملية الإرسال جارية. هذه هي تقنية CSMA (الوصول المتعدد باستشعار الموجة الحاملة). في حالة عدم وجود موجة حاملة، قد يقرر جهاز الإرسال الإرسال بدوره. قد يتخذ عدة أجهزة إرسال هذا القرار. تختلط الإشارات المرسلة معًا: وهذا ما يُسمى بالتصادم. يكتشف جهاز الإرسال هذه الحالة: أثناء الإرسال عبر الكابل، يستمع أيضًا إلى ما يمر عبره بالفعل. إذا اكتشف أن المعلومات التي تنتقل عبر الكابل ليست هي التي أرسلها، يستنتج أن تصادمًا قد حدث وسيتوقف عن الإرسال. وستفعل أجهزة الإرسال الأخرى التي كانت ترسل الشيء نفسه. سيستأنف كل جهاز الإرسال بعد تأخير عشوائي يعتمد على جهاز الإرسال الفردي. تسمى هذه التقنية CD (Collision Detect). وبالتالي، تسمى طريقة الوصول CSMA/CD.
- عنونة 48 بت. لكل جهاز عنوان، يُشار إليه هنا بالعنوان المادي، وهو مكتوب على البطاقة التي تربطه بالكابل. يُسمى هذا العنوان عنوان إيثرنت الجهاز.
طبقة الشبكة
في هذه الطبقة، نجد بروتوكولات IP و ICMP و ARP و RARP.
IP (بروتوكول الإنترنت) | ينقل الحزم بين عقدتين في الشبكة |
ICMP (بروتوكول رسائل التحكم في الإنترنت) | يسهل ICMP الاتصال بين برنامج بروتوكول IP على جهاز ما وبرنامج بروتوكول IP على جهاز آخر. وبالتالي، فهو بروتوكول لتبادل الرسائل داخل بروتوكول IP نفسه. |
ARP (بروتوكول تحليل العناوين) | يربط عنوان الإنترنت الخاص بجهاز ما بعنوانه الفعلي |
RARP (بروتوكول تحويل العناوين العكسي) | يربط العنوان المادي للجهاز بعنوان الإنترنت الخاص به |
طبقات النقل/الجلسة
تتضمن هذه الطبقة البروتوكولات التالية:
TCP (بروتوكول التحكم في الإرسال) | يضمن التسليم الموثوق للمعلومات بين عميلين |
UDP (بروتوكول مخطط بيانات المستخدم) | يضمن توصيل المعلومات بشكل غير موثوق بين عميلين |
طبقات التطبيق/العرض/الجلسة
توجد بروتوكولات متنوعة هنا:
محاكي محطة طرفية يسمح للجهاز A بالاتصال بالجهاز B كمحطة طرفية | |
يتيح نقل الملفات | |
يتيح نقل الملفات | |
يتيح تبادل الرسائل بين مستخدمي الشبكة | |
يحول اسم الجهاز إلى عنوان الإنترنت الخاص به | |
تم إنشاؤه بواسطة Sun Microsystems، ويحدد معيارًا لتمثيل البيانات المستقل عن الجهاز | |
تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يعفي المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR | |
تم تعريفه أيضًا بواسطة Sun، ويسمح هذا البروتوكول لجهاز واحد بـ"رؤية" نظام الملفات الخاص بجهاز آخر. وهو يعتمد على بروتوكول RPC المذكور أعلاه |
8.1.4. كيف تعمل بروتوكولات الإنترنت
تستخدم التطبيقات المطورة في بيئة TCP/IP عمومًا العديد من البروتوكولات في هذه البيئة. يتواصل برنامج التطبيق مع الطبقة العليا من البروتوكولات. تنقل هذه الطبقة المعلومات إلى الطبقة التي تليها، وهكذا دواليك، حتى تصل إلى الوسيط المادي. هناك، يتم نقل المعلومات فعليًا إلى الجهاز الوجهة، حيث تمر عبر الطبقات نفسها مرة أخرى، في الاتجاه المعاكس هذه المرة، حتى تصل إلى التطبيق المقصود لاستلام المعلومات المرسلة. يوضح الرسم البياني التالي مسار المعلومات:

لنأخذ مثالاً: تطبيق FTP، المحدد في طبقة التطبيقات، والذي يتيح نقل الملفات بين الأجهزة.
- يقوم التطبيق بتسليم سلسلة من البايتات ليتم نقلها إلى طبقة النقل.
- تقسم طبقة النقل هذه السلسلة من البايتات إلى مقاطع TCP وتضيف رقم المقطع إلى بداية كل مقطع. يتم تمرير المقاطع إلى طبقة الشبكة، التي يحكمها بروتوكول IP.
- تقوم طبقة IP بإنشاء حزمة تغلف مقطع TCP المستلم. في رأس هذه الحزمة، تضع عناوين الإنترنت لأجهزة المصدر والوجهة. كما تحدد العنوان الفعلي لجهاز الوجهة. يتم تمرير الحزمة بأكملها إلى طبقة ربط البيانات والطبقة المادية، أي إلى بطاقة الشبكة التي تربط الجهاز بالشبكة المادية.
- وهناك، يتم تغليف حزمة IP بدورها في إطار مادي وإرسالها إلى مستلمها عبر الكابل.
- على الجهاز الوجهة، تقوم طبقة الارتباط البياناتي والطبقة المادية بالعكس: فهي تفك تغليف حزمة IP من الإطار المادي وتمررها إلى طبقة IP.
- تتحقق طبقة IP من صحة الحزمة: فهي تحسب مجموعًا اختباريًا استنادًا إلى البتات المستلمة، والتي يجب أن تجدها في رأس الحزمة. إذا لم يكن الأمر كذلك، يتم رفض الحزمة.
- إذا اعتُبرت الحزمة صالحة، تقوم طبقة IP بفك تغليف مقطع TCP الموجود بداخلها وتمريره إلى طبقة النقل.
- تفحص طبقة النقل — وهي طبقة TCP في مثالنا — رقم المقطع للتأكد من أن المقاطع مرتبة بالترتيب الصحيح.
- كما أنها تحسب مجموعًا اختباريًا لمقطع TCP. إذا تبين أنه صحيح، ترسل طبقة TCP إقرارًا بالاستلام إلى الجهاز المصدر؛ وإلا، يتم رفض مقطع TCP.
- كل ما يتبقى لطبقة TCP هو نقل جزء البيانات من المقطع إلى التطبيق المقصود لاستلامه في الطبقة الأعلى.
8.1.5. معالجة المشكلات على الإنترنت
يمكن أن تكون عقدة الشبكة جهاز كمبيوتر أو طابعة ذكية أو خادم ملفات — أي شيء، في الواقع، يمكنه الاتصال باستخدام بروتوكولات TCP/IP. لكل عقدة عنوان مادي بتنسيق يعتمد على نوع الشبكة. في شبكة إيثرنت، يتم ترميز العنوان المادي على 6 بايت. عنوان شبكة X.25 هو رقم مكون من 14 رقمًا.
عنوان الإنترنت للعقدة هو عنوان منطقي: فهو مستقل عن الأجهزة والشبكة المستخدمة. وهو عنوان مكون من 4 بايت يحدد كلاً من الشبكة المحلية والعقدة الموجودة على تلك الشبكة. عادةً ما يتم تمثيل عنوان الإنترنت بأربعة أرقام — قيم البايتات الأربعة — مفصولة بنقطة. وبالتالي، فإن عنوان الجهاز Lagaffe في كلية العلوم في أنجيه هو 193.49.144.1، وعنوان الجهاز Liny هو 193.49.144.9. ومن هذا، يمكننا استنتاج أن عنوان الإنترنت للشبكة المحلية هو 193.49.144.0. يمكن أن يصل عدد العقد في هذه الشبكة إلى 254 عقدة.
نظرًا لأن عناوين الإنترنت، أو عناوين IP، مستقلة عن الشبكة، يمكن لجهاز على الشبكة A التواصل مع جهاز على الشبكة B دون الاهتمام بنوع الشبكة التي يعمل عليها: فهو يحتاج فقط إلى معرفة عنوان IP الخاص به. يتولى بروتوكول IP لكل شبكة التحويل بين عنوان IP والعنوان الفعلي، في كلا الاتجاهين.
يجب أن تكون جميع عناوين IP فريدة. وتقع مسؤولية تخصيصها على عاتق المنظمات الرسمية. في الواقع، تخصص هذه المنظمات عنوانًا للشبكات المحلية، على سبيل المثال 193.49.144.0 لشبكة كلية العلوم في أنجيه. يمكن لمسؤول هذه الشبكة بعد ذلك تخصيص عناوين IP من 193.49.144.1 إلى 193.49.144.254 حسبما يراه مناسبًا. يتم تخزين هذا العنوان عمومًا في ملف محدد على كل جهاز متصل بالشبكة.
8.1.5.1. فئات عناوين IP
عنوان IP هو تسلسل مكون من 4 بايتات، غالبًا ما يُكتب على النحو I1.I2.I3.I4، والذي يحتوي في الواقع على عنوانين:
- عنوان الشبكة
- عنوان عقدة على تلك الشبكة
اعتمادًا على حجم هذين الحقلين، تنقسم عناوين IP إلى 3 فئات: الفئات A و B و C.
الفئة A
عنوان IP: I1.I2.I3.I4 له الشكل R1.N1.N2.N3 حيث
R1 هو عنوان الشبكة
N1.N2.N3 هو عنوان جهاز على تلك الشبكة
وبشكل أكثر دقة، فإن تنسيق عنوان IP من الفئة A هو كما يلي:

يبلغ طول عنوان الشبكة 7 بتات، بينما يبلغ طول عنوان المضيف 24 بتة. وبالتالي، يمكن أن يكون هناك 127 شبكة من الفئة A، تحتوي كل منها على ما يصل إلى 2²⁴ مضيفًا.
الفئة ب
هنا، يكون عنوان IP: I1.I2.I3.I4 على الشكل R1.R2.N1.N2 حيث
R1.R2 هو عنوان الشبكة
N1.N2 هو عنوان جهاز على تلك الشبكة
وبشكل أكثر دقة، فإن تنسيق عنوان IP من الفئة B هو كما يلي:

يبلغ حجم عنوان الشبكة 2 بايت (14 بت، على وجه الدقة)، وكذلك عنوان العقدة. وهذا يعني أنه يمكن أن يكون هناك 2¹⁴ شبكة من الفئة B، تحتوي كل منها على ما يصل إلى 2¹⁶ عقدة.
الفئة C
في هذه الفئة، يكون عنوان IP: I1.I2.I3.I4 على الشكل R1.R2.R3.N1 حيث
R1.R2.R3 هو عنوان الشبكة
N1 هو عنوان جهاز على تلك الشبكة
وبشكل أكثر دقة، فإن تنسيق عنوان IP من الفئة C هو كما يلي:

يبلغ طول عنوان الشبكة 3 بايت (ناقص 3 بتات)، بينما يبلغ طول عنوان المضيف بايت واحد. وبالتالي، يمكن أن يصل عدد شبكات الفئة C إلى 2²¹ شبكة، تحتوي كل منها على ما يصل إلى 256 مضيفًا.
وبما أن عنوان جهاز Lagaffe في كلية العلوم بمدينة أنجيه هو 193.49.144.1، نلاحظ أن البايت الأعلى قيمة هو 193، وهو 11000001 بالثنائي. يمكننا استنتاج أن الشبكة هي شبكة من الفئة C.
العناوين المحجوزة
. بعض عناوين IP هي عناوين شبكات وليست عناوين عقد داخل الشبكة. وهي تلك التي يكون فيها عنوان العقدة محددًا بـ 0. وبالتالي، فإن العنوان 193.49.144.0 هو عنوان IP لشبكة كلية العلوم في أنجيه. وبالتالي، لا يمكن لأي عقدة على الشبكة أن تحمل العنوان صفر.
. عندما يتكون عنوان العقدة في عنوان IP بالكامل من أرقام 1، فإنه يكون عنوان بث: يشير هذا العنوان إلى جميع العقد الموجودة على الشبكة.
. في شبكة من الفئة C، التي تسمح نظريًا بـ 2⁸ = 256 عقدة، إذا أزلنا العنوانين المحظورين، يتبقى لدينا 254 عنوانًا مسموحًا به فقط.
8.1.5.2. بروتوكولات تحويل عنوان الإنترنت <--> العنوان الفعلي
لقد رأينا أنه عند نقل البيانات من جهاز إلى آخر، يتم تغليفها في حزم أثناء مرورها عبر طبقة IP. تتخذ هذه الحزم الشكل التالي:

وبالتالي، تحتوي حزمة IP على عناوين الإنترنت للجهازين المصدر والوجهة. وعندما يتم إرسال هذه الحزمة إلى الطبقة المسؤولة عن إرسالها عبر الشبكة المادية، تتم إضافة معلومات إضافية لتشكيل الإطار المادي الذي سيتم إرساله في النهاية عبر الشبكة. على سبيل المثال، يكون تنسيق الإطار على شبكة إيثرنت كما يلي:

في الإطار النهائي، توجد العناوين المادية للجهازين المصدر والوجهة. كيف يتم الحصول عليها؟
يستطيع الجهاز المرسل، الذي يعرف عنوان IP للجهاز الذي يريد التواصل معه، الحصول على العنوان المادي لهذا الأخير باستخدام بروتوكول محدد يسمى ARP (بروتوكول تحليل العناوين).
- ترسل الجهاز نوعًا خاصًا من الحزم يُسمى حزمة ARP تحتوي على عنوان IP للجهاز الذي يتم البحث عن عنوانه الفعلي. كما تتضمن الحزمة عنوان IP الخاص بها وعنوانها الفعلي.
- يتم إرسال هذه الحزمة إلى جميع العقد الموجودة على الشبكة.
- وتتعرف هذه العقد على الطبيعة الخاصة للحزمة. وتستجيب العقدة التي تتعرف على عنوان IP الخاص بها في الحزمة بإرسال عنوانها الفعلي إلى مرسل الحزمة. كيف يمكنها القيام بذلك؟ لقد عثرت على عنوان IP وعنوان مرسل الحزمة الفعليين في الحزمة.
- وبالتالي، يتلقى المرسل العنوان الفعلي الذي كان يبحث عنه. ويخزنه في الذاكرة حتى يتمكن من استخدامه لاحقًا إذا لزم إرسال حزم أخرى إلى نفس المستلم.
يتم عادةً تخزين عنوان IP للجهاز في أحد ملفات التكوين الخاصة به، والتي يمكنه الرجوع إليها لاسترداده. يمكن تغيير هذا العنوان: ما عليك سوى تعديل الملف. ومع ذلك، يتم تخزين العنوان الفعلي في ذاكرة بطاقة الشبكة ولا يمكن تغييره.
عندما يرغب المسؤول في إعادة تنظيم الشبكة، قد يحتاج إلى تغيير عناوين IP لجميع العقد وبالتالي تعديل ملفات التكوين المختلفة لكل عقدة. قد يكون هذا الأمر مملًا وعرضة للأخطاء إذا كان هناك العديد من الأجهزة. تتضمن إحدى الطرق عدم تخصيص عنوان IP للأجهزة: بدلاً من ذلك، يتم إدخال رمز خاص في الملف حيث تجد الجهاز عادةً عنوان IP الخاص به. عندما تكتشف الجهاز أنه لا يوجد لديه عنوان IP، يطلب الجهاز عنوانًا باستخدام بروتوكول يسمى RARP (بروتوكول تحليل العناوين العكسي). ثم يرسل حزمة خاصة تسمى حزمة RARP عبر الشبكة — مشابهة لحزمة ARP السابقة — تحتوي على عنوانه الفعلي. يتم إرسال هذه الحزمة إلى جميع العقد التي تتعرف على حزمة RARP. تحتفظ إحدى هذه العقد، والتي تسمى خادم RARP، بملف يحتوي على تخطيطات العناوين المادية <--> عناوين IP لجميع العقد. ثم تستجيب لمرسل حزمة RARP بإرسال عنوان IP الخاص بها. لذلك، لا يحتاج المسؤول الذي يرغب في إعادة تكوين شبكته سوى تعديل ملف التخطيط على خادم RARP ( ). يجب أن يكون لهذا الخادم عادةً عنوان IP ثابت يجب أن يكون قادرًا على معرفته دون الحاجة إلى استخدام بروتوكول RARP نفسه.
8.1.6. طبقة الشبكة، المعروفة باسم طبقة IP للإنترنت
يحدد بروتوكول الإنترنت (IP) التنسيق الذي يجب أن تتخذه الحزم وكيفية التعامل معها أثناء الإرسال أو الاستقبال. يُطلق على هذا النوع المحدد من الحزم اسم مخطط بيانات IP. وقد ناقشنا ذلك سابقًا:

النقطة المهمة هي أنه، بالإضافة إلى البيانات المراد إرسالها، تحتوي مخطط بيانات IP على عناوين الإنترنت لأجهزة المصدر والوجهة. وبهذه الطريقة، تعرف الجهاز المستقبل من الذي يرسل له الرسالة.
على عكس إطار الشبكة، الذي يتحدد طوله بالخصائص المادية للشبكة التي يمر عبرها، فإن طول مخطط بيانات IP محدد بواسطة البرنامج، وبالتالي سيكون هو نفسه عبر الشبكات المادية المختلفة. لقد رأينا أنه كلما انتقلنا من طبقة الشبكة إلى الطبقة المادية، يتم تغليف مخطط بيانات IP داخل إطار مادي. وقد ضربنا مثالاً بالإطار المادي لشبكة إيثرنت:

تنتقل الإطارات المادية من عقدة إلى أخرى باتجاه وجهتها، والتي قد لا تكون على نفس الشبكة المادية التي توجد عليها الجهاز المرسل. وبالتالي، قد يتم تغليف حزمة IP تباعًا في إطارات مادية مختلفة عند العقد التي تربط بين شبكتين من أنواع مختلفة. ومن الممكن أيضًا أن تكون حزمة IP كبيرة جدًا بحيث لا يمكن تغليفها في إطار مادي واحد. ثم يقوم برنامج IP الموجود على العقدة التي تنشأ فيها هذه المشكلة بتقسيم حزمة IP إلى أجزاء وفقًا لقواعد محددة، يتم إرسال كل منها بعد ذلك عبر الشبكة المادية. ولن يتم إعادة تجميعها إلا في وجهتها النهائية.
8.1.6.1. التوجيه
التوجيه هو طريقة توجيه حزم بروتوكول الإنترنت (IP) إلى وجهتها. وهناك طريقتان: التوجيه المباشر والتوجيه غير المباشر.
التوجيه المباشر
يشير التوجيه المباشر إلى إرسال حزمة IP مباشرة من المرسل إلى المستلم داخل نفس الشبكة:
- يحتوي الجهاز الذي يرسل حزمة بيانات IP على عنوان IP الخاص بالمستلم.
- ويحصل على العنوان الفعلي للمستلم عبر بروتوكول ARP أو من جداوله، إذا كان قد تم الحصول على هذا العنوان بالفعل.
- ويقوم بإرسال الحزمة عبر الشبكة إلى ذلك العنوان الفعلي.
التوجيه غير المباشر
يشير التوجيه غير المباشر إلى إرسال حزمة IP إلى وجهة تقع على شبكة أخرى غير تلك التي ينتمي إليها المرسل. في هذه الحالة، تختلف أجزاء عناوين الشبكة الخاصة بعناوين IP لأجهزة المصدر والوجهة. يتعرف الجهاز المصدر على ذلك. ثم يرسل الحزمة إلى عقدة خاصة تسمى جهاز التوجيه، وهي عقدة تربط شبكة محلية بشبكات أخرى ويجد عنوان IP الخاص بها في جداوله — وهو عنوان تم الحصول عليه في البداية إما من ملف، أو من ذاكرة دائمة، أو عبر معلومات متداولة على الشبكة.
يتم توصيل جهاز التوجيه بشبكتين وله عنوان IP داخل كلتا الشبكتين.

في المثال أعلاه:
- الشبكة رقم 1 لها عنوان الإنترنت 193.49.144.0 والشبكة رقم 2 لها العنوان 193.49.145.0.
- داخل الشبكة رقم 1، يمتلك جهاز التوجيه العنوان 193.49.144.6، ويمتلك العنوان 193.49.145.3 داخل الشبكة رقم 2.
تتمثل مهمة جهاز التوجيه في أخذ حزمة IP التي يتلقاها — والموجودة داخل إطار مادي نموذجي للشبكة رقم 1 — ووضعها في إطار مادي يمكنه الانتقال عبر الشبكة رقم 2. إذا كان عنوان IP لوجهة الحزمة موجودًا داخل الشبكة رقم 2، فسيرسل جهاز التوجيه الحزمة إليها مباشرةً؛ وإلا، فسيرسلها إلى جهاز توجيه آخر، يربط الشبكة رقم 2 بالشبكة رقم 3، وهكذا دواليك.
8.1.6.2. رسائل الخطأ والتحكم
يوجد أيضًا ضمن طبقة الشبكة — على نفس مستوى بروتوكول IP — بروتوكول ICMP (بروتوكول رسائل التحكم في الإنترنت). ويُستخدم لإرسال رسائل تتعلق بالتشغيل الداخلي للشبكة: العقد المعطلة، والازدحام في جهاز التوجيه، وما إلى ذلك. يتم تغليف رسائل ICMP في حزم IP وإرسالها عبر الشبكة. تتخذ طبقات IP للعقد المختلفة الإجراءات المناسبة بناءً على رسائل ICMP التي تتلقاها. وبالتالي، لا يرى التطبيق نفسه أبدًا هذه المشكلات الخاصة بالشبكة.
ستستخدم العقدة معلومات ICMP لتحديث جداول التوجيه الخاصة بها.
8.1.7. طبقة النقل: بروتوكولا UDP و TCP
8.1.7.1. بروتوكول UDP: بروتوكول مخطط بيانات المستخدم
يسمح بروتوكول UDP بتبادل غير موثوق للبيانات بين نقطتين، مما يعني أن التسليم الناجح لحزمة إلى وجهتها غير مضمون. يمكن للتطبيق، إذا اختار ذلك، التعامل مع هذا الأمر بنفسه، على سبيل المثال عن طريق انتظار إقرار بالاستلام بعد إرسال رسالة قبل إرسال الرسالة التالية.
حتى الآن، على مستوى الشبكة، ناقشنا عناوين IP للأجهزة. ومع ذلك، على جهاز واحد، يمكن أن تتعايش عمليات مختلفة في وقت واحد، ويمكن لجميعها التواصل مع بعضها البعض. لذلك، عند إرسال رسالة، من الضروري تحديد ليس فقط عنوان IP للجهاز الوجهة، ولكن أيضًا "اسم" العملية الوجهة. هذا الاسم هو في الواقع رقم، يُسمى رقم المنفذ. يتم حجز أرقام معينة للتطبيقات القياسية: المنفذ 69 لتطبيق TFTP (بروتوكول نقل الملفات البسيط)، على سبيل المثال.
تُسمى الحزم التي يعالجها بروتوكول UDP أيضًا بالدياتاجرامات. وهي تأخذ الشكل التالي:

يتم تغليف هذه المخططات البياناتية في حزم IP، ثم في إطارات مادية.
8.1.7.2. بروتوكول TCP: بروتوكول التحكم في الإرسال
بالنسبة للاتصالات الآمنة، لا يكفي بروتوكول UDP: يجب على مطور التطبيق إنشاء بروتوكول بنفسه لضمان توجيه الحزم بشكل صحيح.
يتجنب بروتوكول TCP (بروتوكول التحكم في الإرسال) هذه المشاكل. وفيما يلي خصائصه:
- تقوم العملية التي ترغب في الإرسال أولاً بإنشاء اتصال مع العملية التي تستقبل المعلومات التي توشك على إرسالها. يتم إجراء هذا الاتصال بين منفذ على الجهاز المرسل ومنفذ على الجهاز المستقبل. وبذلك يتم إنشاء مسار افتراضي بين المنفذين، والذي سيتم حجزه حصريًا للعمليتين اللتين أنشأتا الاتصال.
- تتبع جميع الحزم المرسلة من قبل العملية المصدر هذا المسار الافتراضي وتصل بالترتيب الذي تم إرسالها به، وهو ما لم يكن مضمونًا في بروتوكول UDP نظرًا لأن الحزم يمكن أن تتبع مسارات مختلفة.
- تبدو البيانات المرسلة متصلة. ترسل العملية المرسلة البيانات وفقًا لسرعتها الخاصة. لا يتم إرسال هذه البيانات بالضرورة على الفور: ينتظر بروتوكول TCP حتى يتوفر لديه ما يكفي لإرساله. يتم تخزينها في بنية تسمى مقطع TCP. بمجرد امتلاء هذا المقطع، يتم إرساله إلى طبقة IP، حيث يتم تغليفه في حزمة IP.
- يتم ترقيم كل مقطع يرسله بروتوكول TCP. يتحقق بروتوكول TCP المستلم من أنه يتلقى المقاطع بالتسلسل. لكل مقطع يتم استلامه بشكل صحيح، يرسل إقرارًا بالاستلام إلى المرسل.
- وعندما يستلمها المستقبل، يقوم بإخطار المرسل. وبذلك يمكن للمرسل التأكد من أن المقطع قد تم تسليمه بنجاح، وهو ما لم يكن ممكنًا مع بروتوكول UDP.
- إذا لم يتلق بروتوكول TCP الذي أرسل مقطعًا ما إقرارًا بالاستلام بعد مرور فترة زمنية معينة، فإنه يعيد إرسال المقطع المعني، مما يضمن جودة خدمة توصيل المعلومات.
- الدائرة الافتراضية التي يتم إنشاؤها بين عمليتي الاتصال هي دائرة ثنائية الاتجاه: وهذا يعني أن المعلومات يمكن أن تتدفق في كلا الاتجاهين. وبالتالي، يمكن لعملية الوجهة إرسال إقرارات حتى أثناء استمرار عملية المصدر في إرسال المعلومات. وهذا يسمح، على سبيل المثال، لبروتوكول TCP المصدر بإرسال شرائح متعددة دون انتظار إقرار. وإذا أدرك، بعد فترة زمنية معينة، أنه لم يتلق إقرارًا باستلام مقطع معين رقم n، فسيستأنف إرسال المقاطع من تلك النقطة.
8.1.8. طبقة التطبيق
فوق بروتوكولي UDP و TCP، توجد بروتوكولات قياسية متنوعة:
TELNET
يسمح هذا البروتوكول لمستخدم على الجهاز <bdi dir="ltr" class="odt-ltr-term">A</bdi> على الشبكة بالاتصال بالجهاز <bdi dir="ltr" class="odt-ltr-term">B</bdi> (الذي يُسمى غالبًا الجهاز المضيف). يحاكي <bdi dir="ltr" class="odt-ltr-term">TELNET</bdi> ما يُسمى بالمحطة الطرفية العالمية على الجهاز <bdi dir="ltr" class="odt-ltr-term">A.</bdi> وبالتالي، يتصرف المستخدم كما لو كان لديه محطة طرفية متصلة بالجهاز <bdi dir="ltr" class="odt-ltr-term">B.</bdi> يعتمد <bdi dir="ltr" class="odt-ltr-term">Telnet</bdi> على بروتوكول <bdi dir="ltr" class="odt-ltr-term">TCP.</bdi>
FTP: (بروتوكول نقل الملفات)
يتيح هذا البروتوكول تبادل الملفات بين جهازين بعيدين، بالإضافة إلى إجراء عمليات على الملفات مثل إنشاء الدلائل، على سبيل المثال. ويعتمد على بروتوكول <bdi dir="ltr" class="odt-ltr-term">TCP.</bdi>
TFTP: (بروتوكول نقل الملفات البسيط)
هذا البروتوكول هو نوع من أنواع بروتوكول <bdi dir="ltr" class="odt-ltr-term">FTP.</bdi> يعتمد على بروتوكول <bdi dir="ltr" class="odt-ltr-term">UDP</bdi> وهو أقل تعقيدًا من بروتوكول <bdi dir="ltr" class="odt-ltr-term">FTP.</bdi>
DNS: (نظام أسماء النطاقات)
عندما يرغب المستخدم في تبادل الملفات مع جهاز بعيد، عبر <bdi dir="ltr" class="odt-ltr-term">FTP</bdi> على سبيل المثال، يجب أن يعرف عنوان الإنترنت الخاص بذلك الجهاز. على سبيل المثال، لاستخدام <bdi dir="ltr" class="odt-ltr-term">FTP</bdi> على جهاز <bdi dir="ltr" class="odt-ltr-term">Lagaffe</bdi> في جامعة أنجيه، ستحتاج إلى تشغيل <bdi dir="ltr" class="odt-ltr-term">FTP</bdi> على النحو التالي: <bdi dir="ltr" class="odt-ltr-term">FTP 193.49.144.1</bdi>
وهذا يتطلب وجود دليل يربط الأجهزة بعناوين <bdi dir="ltr" class="odt-ltr-term">IP.</bdi> وفي هذا الدليل، من المرجح أن يتم تعيين الأجهزة بأسماء رمزية مثل:
جهاز <bdi dir="ltr" class="odt-ltr-term">DPX2/320</bdi> في جامعة أنجيه
جهاز <bdi dir="ltr" class="odt-ltr-term">Sun</bdi> في <bdi dir="ltr" class="odt-ltr-term">ISERPA</bdi> في أنجيه
من الواضح أنه سيكون من الأنسب الإشارة إلى جهاز ما باسمه بدلاً من عنوان <bdi dir="ltr" class="odt-ltr-term">IP</bdi> الخاص به. وهذا يثير مسألة تفرد الأسماء: فهناك ملايين الأجهزة المتصلة ببعضها البعض. قد يتصور المرء وجود هيئة مركزية تقوم بتعيين الأسماء. ولا شك أن ذلك سيكون أمراً مرهقاً للغاية. وقد تم في الواقع توزيع السيطرة على الأسماء عبر **النطاقات**. تدار كل نطاق من قبل منظمة صغيرة الحجم عمومًا تتمتع بحرية كاملة في اختيار أسماء الأجهزة. وبالتالي، تنتمي الأجهزة في فرنسا إلى نطاق **fr**، الذي تديره <bdi dir="ltr" class="odt-ltr-term">Inria</bdi> في باريس. ولتبسيط الأمور أكثر، يتم توزيع السيطرة بشكل أكبر: يتم إنشاء نطاقات داخل نطاق **fr**. وبالتالي، تنتمي جامعة أنجيه إلى نطاق **<bdi dir="ltr" class="odt-ltr-term">univ-Angers</bdi>**. ويتمتع القسم الذي يدير هذا النطاق بحرية كاملة في تسمية الأجهزة الموجودة على شبكة جامعة أنجيه. في الوقت الحالي، لم يتم تقسيم هذا النطاق إلى أقسام فرعية. ولكن في جامعة كبيرة تضم العديد من الأجهزة المتصلة بالشبكة، قد يتم ذلك.
تم تسمية جهاز <bdi dir="ltr" class="odt-ltr-term">DPX2/320</bdi> في جامعة أنجيه *باسم* <bdi dir="ltr" class="odt-ltr-term">Lagaffe</bdi>، بينما تم تسمية جهاز كمبيوتر شخصي 486DX50 *باسم <bdi dir="ltr" class="odt-ltr-term">liny</bdi>*. كيف يمكنك الإشارة إلى هذه الأجهزة من الخارج؟ عن طريق تحديد التسلسل الهرمي للنطاقات التي تنتمي إليها. وبالتالي، سيكون الاسم الكامل لجهاز <bdi dir="ltr" class="odt-ltr-term">Lagaffe</bdi> هو:
يمكن استخدام الأسماء النسبية داخل النطاقات. وبالتالي، داخل نطاق **fr** وخارج نطاق **<bdi dir="ltr" class="odt-ltr-term">univ</bdi>**-<bdi dir="ltr" class="odt-ltr-term">Angers</bdi>، يمكن الإشارة إلى جهاز <bdi dir="ltr" class="odt-ltr-term">Lagaffe</bdi> على النحو التالي
وأخيرًا، داخل نطاق *<bdi dir="ltr" class="odt-ltr-term">univ-Angers</bdi>*، يمكن الإشارة إليها ببساطة على النحو التالي
وبالتالي، يمكن للتطبيق الإشارة إلى جهاز ما باسمه. لكن في النهاية، لا تزال بحاجة إلى الحصول على عنوان <bdi dir="ltr" class="odt-ltr-term">IP</bdi> الخاص بالجهاز. كيف يتم ذلك؟ لنفترض أننا نريد، من الجهاز <bdi dir="ltr" class="odt-ltr-term">A</bdi>، التواصل مع الجهاز <bdi dir="ltr" class="odt-ltr-term">B.</bdi>
- إذا كان الجهاز B ينتمي إلى نفس المجال الذي ينتمي إليه الجهاز A، فمن المرجح أن يتم العثور على عنوان IP الخاص به في ملف موجود على الجهاز A.
- وإلا، فسيجد الجهاز A، في ملف آخر أو في نفس الملف السابق، قائمة بعدة خوادم أسماء مع عناوين IP الخاصة بها. وخادم الأسماء مسؤول عن ربط اسم الجهاز بعنوان IP الخاص به. وسيقوم الجهاز A بإرسال طلب خاص إلى أول خادم أسماء في قائمته، يُعرف باسم استعلام DNS، والذي يتضمن اسم الجهاز المطلوب. إذا كان الخادم المستفسر عنه يحتوي على هذا الاسم في سجلاته، فسيرسل عنوان IP المقابل إلى الجهاز A. وإلا، فسيجد الخادم أيضًا في ملفاته قائمة بخوادم الأسماء التي يمكنه الاستفسار منها. ثم يقوم بذلك. وبالتالي، سيتم الاستفسار من عدد من خوادم الأسماء، ليس بشكل عشوائي بل بطريقة تقلل من عدد الطلبات. إذا تم العثور على الجهاز أخيرًا، فسيتم إرسال الرد إلى الجهاز A.
XDR: (تمثيل البيانات الخارجية)
تم إنشاء هذا البروتوكول بواسطة شركة <bdi dir="ltr" class="odt-ltr-term">Sun Microsystems</bdi>، وهو يحدد معيارًا لتمثيل البيانات بشكل مستقل عن الجهاز.
RPC: (استدعاء الإجراءات عن بُعد)
تم تعريفه أيضًا بواسطة <bdi dir="ltr" class="odt-ltr-term">Sun</bdi>، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يعفي المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول <bdi dir="ltr" class="odt-ltr-term">XDR</bdi>
NFS: نظام ملفات الشبكة
تم تعريفه أيضًا بواسطة <bdi dir="ltr" class="odt-ltr-term">Sun</bdi>، ويسمح هذا البروتوكول لجهاز واحد بـ"رؤية" نظام الملفات الخاص بجهاز آخر. وهو يعتمد على بروتوكول <bdi dir="ltr" class="odt-ltr-term">RPC</bdi> الموصوف أعلاه.
8.1.9. الخلاصة
في هذه المقدمة، عرضنا بعض الميزات الرئيسية لبروتوكولات الإنترنت. لمزيد من الاستكشاف في هذا المجال، يمكن للقراء الرجوع إلى كتاب دوغلاس كومر الممتاز:
العنوان: TCP/IP: الهندسة المعمارية والبروتوكولات والتطبيقات.
المؤلف: دوغلاس كومر
الناشر: InterEditions
8.2. إدارة عناوين الشبكة في Java
8.2.1. التعريف
يتم تعريف كل جهاز على الإنترنت بواسطة عنوان أو اسم فريد. تتم إدارة هذين العنصرين في Java بواسطة فئة InetAddress، التي تتضمن الطرق التالية:
تُرجع 4 بايت من عنوان IP لمثيل InetAddress الحالي | |
تُرجع عنوان IP لمثيل InetAddress الحالي | |
تُرجع اسم الإنترنت لمثيل InetAddress الحالي | |
تُرجع عنوان IP/اسم الإنترنت لمثيل InetAddress الحالي | |
ينشئ مثيل InetAddress للجهاز المحدد بواسطة Host. يرمي استثناءً إذا كان Host غير معروف. يمكن أن يكون Host اسم الإنترنت لجهاز أو عنوان IP الخاص به بالصيغة I1.I2.I3.I4 | |
ينشئ مثيل InetAddress للجهاز الذي يعمل عليه البرنامج الذي يحتوي على هذه التعليمات. |
8.2.2. بعض الأمثلة
8.2.2.1. تحديد الجهاز المحلي
import java.net.*;
public class localhost{
public static void main (String arg[]){
try{
InetAddress adresse=InetAddress.getLocalHost();
byte[] IP=adresse.getAddress();
System.out.print("IP=");
int i;
for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
System.out.println(IP[i]);
System.out.println("adresse="+adresse.getHostAddress());
System.out.println("nom="+adresse.getHostName());
System.out.println("identité="+adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getLocalHost : "+e);
}// fin try
}// fine hand
}// fin class
نتائج التنفيذ هي كما يلي:
كل جهاز له عنوان IP داخلي، وهو 127.0.0.1. عندما يستخدم برنامج ما هذا العنوان الشبكي، فإنه يشير إلى الجهاز الذي يعمل عليه. ميزة هذا العنوان هي أنه لا يتطلب بطاقة شبكة. وهذا يعني أنه يمكنك اختبار برامج الشبكة دون أن تكون متصلاً بشبكة. هناك طريقة أخرى للإشارة إلى الجهاز المحلي وهي استخدام الاسم localhost.
8.2.2.2. تحديد أي جهاز
import java.net.*;
public class getbyname{
public static void main (String arg[]){
String nomMachine;
// we retrieve the argument
if(arg.length==0)
nomMachine="localhost";
else nomMachine=arg[0];
// we try to obtain the machine address
try{
InetAddress adresse=InetAddress.getByName(nomMachine);
System.out.println("IP : "+ adresse.getHostAddress());
System.out.println("nom : "+ adresse.getHostName());
System.out.println("identité : "+ adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getByName : "+e);
}// fin try
}// fine hand
}// fin class
باستخدام استدعاء getByName في Java، نحصل على النتائج التالية:
باستخدام استدعاء getbyname في Java shiva.istia.univ-angers.fr، نحصل على:
باستخدام استدعاء Java **getbyname www.ibm.com**، نحصل على:
8.3. برمجة TCP-IP
8.3.1. معلومات عامة

عندما يرغب تطبيق AppA على الجهاز A في التواصل مع تطبيق AppB على الجهاز B عبر الإنترنت، فإنه يحتاج إلى معرفة عدة أمور:
- عنوان IP أو اسم المضيف للجهاز B
- رقم المنفذ الذي يستخدمه تطبيق AppB. وذلك لأن الجهاز B قد يستضيف العديد من التطبيقات التي تعمل على الإنترنت. وعندما يتلقى معلومات من الشبكة، يجب أن يعرف التطبيق الذي تستهدفه هذه المعلومات. وتصل التطبيقات الموجودة على الجهاز B إلى الشبكة من خلال واجهات تُعرف أيضًا باسم منافذ الاتصال. وترد هذه المعلومات في الحزمة التي يتلقاها الجهاز B حتى يمكن توصيلها إلى التطبيق الصحيح.
- بروتوكولات الاتصال التي يفهمها الجهاز B. في دراستنا، سنستخدم بروتوكولات TCP-IP فقط.
- بروتوكول الاتصال الذي يقبله تطبيق AppB. في الواقع، ستتواصل الجهازان A و B مع بعضهما البعض. وسيتم تغليف ما يقولانه ضمن بروتوكولات TCP/IP. ومع ذلك، عندما يتلقى تطبيق AppB، في نهاية السلسلة، المعلومات المرسلة من تطبيق AppA، يجب أن يكون قادرًا على تفسيرها. وهذا مشابه للحالة التي يتواصل فيها شخصان، A و B، عبر الهاتف: حيث يتم نقل محادثتهما عبر الهاتف. يتم ترميز الكلام كإشارات بواسطة الهاتف A، ويتم إرساله عبر خطوط الهاتف، ويستقبله الهاتف B، حيث يتم فك ترميزه. ثم يسمع الشخص B الكلام. وهنا يأتي دور مفهوم بروتوكول الاتصال: إذا كان A يتحدث الفرنسية و B لا يفهم تلك اللغة، فلن يتمكن A و B من التواصل بشكل فعال.
لذلك، يجب أن يتفق التطبيقان المتواصلان على نوع الحوار الذي سيستخدمانه. على سبيل المثال، الحوار مع خدمة FTP يختلف عن الحوار مع خدمة POP: هاتان الخدمتان لا تقبلان نفس الأوامر. فلهما بروتوكول حوار مختلف.
8.3.2. خصائص بروتوكول TCP
هنا، سنقوم فقط بفحص اتصالات الشبكة باستخدام بروتوكول النقل TCP. دعونا نستعرض خصائص هذا البروتوكول:
- تقوم العملية التي ترغب في إرسال البيانات أولاً بإنشاء اتصال مع العملية التي ستستقبل المعلومات التي توشك على إرسالها. يتم إنشاء هذا الاتصال بين منفذ على الجهاز المرسل ومنفذ على الجهاز المستقبل. وبالتالي يتم إنشاء مسار افتراضي بين المنفذين، والذي سيتم حجزه حصريًا للعمليتين اللتين قامتا بإنشاء الاتصال.
- تتبع جميع الحزم المرسلة من قبل العملية المصدر هذا المسار الافتراضي وتصل بالترتيب الذي تم إرسالها به
- تبدو البيانات المرسلة متصلة. ترسل العملية المرسلة البيانات وفقًا لسرعتها الخاصة. لا يتم إرسال هذه البيانات بالضرورة على الفور: ينتظر بروتوكول TCP حتى يتوفر لديه ما يكفي لإرساله. يتم تخزينها في بنية تسمى مقطع TCP. بمجرد امتلاء هذا المقطع، يتم إرساله إلى طبقة IP، حيث يتم تغليفه في حزمة IP.
- يتم ترقيم كل مقطع يرسله بروتوكول TCP. يتحقق بروتوكول TCP المستقبل من أنه يتلقى المقاطع بالتسلسل. لكل مقطع يتم استلامه بشكل صحيح، يرسل إقرارًا بالاستلام إلى المرسل.
- عندما يتلقى المرسل هذا الإقرار، يقوم بإخطار عملية الإرسال. وبذلك يمكن لعملية الإرسال التأكد من وصول المقطع بأمان.
- إذا لم يتلق بروتوكول TCP الذي أرسل المقطع إقرارًا بالاستلام بعد مرور فترة زمنية معينة، فإنه يعيد إرسال المقطع المعني، مما يضمن جودة خدمة توصيل المعلومات.
- الدائرة الافتراضية التي يتم إنشاؤها بين عمليتي الاتصال هي دائرة ثنائية الاتجاه: وهذا يعني أن المعلومات يمكن أن تتدفق في كلا الاتجاهين. وبالتالي، يمكن لعملية الوجهة إرسال إقرارات حتى أثناء استمرار عملية المصدر في إرسال المعلومات. وهذا يسمح، على سبيل المثال، لبروتوكول TCP المصدر بإرسال شرائح متعددة دون انتظار إقرار. وإذا أدرك، بعد فترة زمنية معينة، أنه لم يتلق إقرارًا باستلام مقطع معين رقم n، فسيستأنف إرسال المقاطع من تلك النقطة.
8.3.3. العلاقة بين العميل والخادم
غالبًا ما يكون الاتصال عبر الإنترنت غير متماثل: تبدأ الآلة أ الاتصال لطلب خدمة من الآلة ب، محددةً أنها تريد فتح اتصال مع الخدمة SB1 على الآلة ب. تقبل الآلة ب أو ترفض. إذا وافق، يمكن للجهاز A إرسال طلباته إلى الخدمة SB1. يجب أن تتوافق هذه الطلبات مع بروتوكول الاتصال الذي تفهمه الخدمة SB1. وبذلك يتم إنشاء حوار طلب-استجابة بين الجهاز A، المعروف باسم جهاز العميل، والجهاز B، المعروف باسم جهاز الخادم. سيقوم أحد الشريكين بإغلاق الاتصال.
8.3.4. بنية العميل
ستكون بنية برنامج الشبكة الذي يطلب خدمات تطبيق الخادم كما يلي:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
8.3.5. بنية الخادم
ستكون بنية البرنامج الذي يقدم الخدمات على النحو التالي:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
يعالج برنامج الخادم طلب الاتصال الأولي للعميل بشكل مختلف عن طلباته اللاحقة للخدمة. لا يقدم البرنامج الخدمة بنفسه. وإذا فعل ذلك، فلن يستمر في الاستماع لطلبات الاتصال أثناء تقديم الخدمة، ولن يتم تلبية احتياجات العملاء. ولذلك، فإنه يتصرف بطريقة مختلفة: بمجرد استلام طلب اتصال على منفذ الاستماع وقبوله، يقوم الخادم بإنشاء مهمة مسؤولة عن تقديم الخدمة التي طلبها العميل. يتم تقديم هذه الخدمة على منفذ آخر بجهاز الخادم يُسمى منفذ الخدمة. وهذا يسمح بتلبية احتياجات عدة عملاء في نفس الوقت.
ستكون مهمة الخدمة بالهيكل التالي:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
8.3.6. فئة Socket
8.3.6.1. التعريف
الأداة الأساسية التي تستخدمها البرامج التي تتواصل عبر الإنترنت هي «السوكت» (socket). وتعني هذه الكلمة الإنجليزية «مأخذ كهربائي»، وقد تم توسيع معناها هنا لتشير إلى «مأخذ شبكي». لكي يتمكن التطبيق من إرسال واستقبال المعلومات عبر الإنترنت، فإنه يحتاج إلى منفذ شبكة، أي مقبس. تم إنشاء هذه الأداة في الأصل في إصدارات بيركلي من نظام يونكس. ومنذ ذلك الحين تم نقلها إلى جميع أنظمة يونكس وكذلك إلى بيئة ويندوز. كما أنها موجودة على الآلات الافتراضية لـ جافا في شكلين: فئة Socket لتطبيقات العميل وفئة ServerSocket لتطبيقات الخادم. نوضح هنا بعض منشئات وأساليب فئة Socket:
يفتح اتصالاً عن بُعد بالمنفذ port على الجهاز المضيف |
تُرجع رقم المنفذ المحلي الذي يستخدمه المأخذ | |||
تُرجع رقم المنفذ البعيد الذي يتصل به المقبس | |||
تُرجع عنوان InetAddress المحلي الذي يرتبط به المأخذ | |||
تُرجع عنوان InetAddress البعيد الذي يرتبط به المأخذ | |||
تُرجع دفق إدخال يُستخدم لقراءة البيانات المرسلة من الشريك البعيد | |||
تُرجع تيار إخراج يُستخدم لإرسال البيانات إلى الشريك البعيد | |||
يغلق تيار الإدخال الخاص بالمقبس | |||
يغلق دفق الإخراج للمقبس | |||
يغلق المأخذ وتدفقات الإدخال/الإخراج الخاصة به | |||
تُرجع سلسلة "تمثل" المأخذ | |||
8.3.6.2. إنشاء اتصال بخادم
لقد رأينا أنه لكي يقوم الجهاز A بإنشاء اتصال بخدمة على الجهاز B، فإنه يحتاج إلى معلومتين:
- عنوان IP أو اسم المضيف للجهاز B
- رقم المنفذ الذي تعمل عليه الخدمة المطلوبة
المنشئ
يُنشئ مأخذ توصيل ويقوم بتوصيله بجهاز host على المنفذ port. يُطلق مُنشئ الدالة هذا استثناءً في حالات مختلفة:
- عنوان غير صحيح
- منفذ غير صحيح
- رفض الطلب
- …
نحتاج إلى معالجة هذا الاستثناء:
Socket sClient=null;
try{
sClient=new Socket(host,port);
} catch(Exception e){
// la connexion a échoué - on traite l'erreur
….
}
إذا نجح طلب الاتصال، يتم تخصيص منفذ محلي للعميل للتواصل مع الجهاز ب. وبمجرد إقامة الاتصال، يمكن استرداد هذا المنفذ باستخدام الطريقة التالية:
إذا نجح الاتصال، فقد رأينا أن الخادم، من جانبه، لديه مهمة أخرى تتمثل في معالجة الخدمة على ما يُسمى بمنفذ الخدمة. يمكن الحصول على رقم هذا المنفذ باستخدام الطريقة:
8.3.6.3. إرسال المعلومات عبر الشبكة
يمكنك الحصول على دفق كتابة على المقبس — وبالتالي على الشبكة — باستخدام الطريقة:
سيتم استلام كل ما يتم إرساله إلى هذا الدفق على منفذ الخدمة الخاص بجهاز الخادم. تستخدم العديد من التطبيقات واجهة نصية تتكون من أسطر نصية متبوعة بحرف سطر جديد. ولذلك، فإن طريقة println مفيدة جدًا في هذه الحالات. ثم نقوم بتحويل دفق الإخراج OutputStream إلى دفق PrintWriter، الذي يوفر طريقة println. قد يؤدي الكتابة إلى إنشاء استثناء.
8.3.6.4. قراءة المعلومات من الشبكة
يمكنك الحصول على تيار قراءة للبيانات الواردة على المقبس باستخدام الطريقة:
كل ما يتم قراءته من هذا الدفق يأتي من منفذ الخدمة الخاص بجهاز الخادم. بالنسبة للتطبيقات التي تحتوي على مربع حوار يتكون من أسطر نصية تنتهي بسطر جديد، سنرغب في استخدام طريقة readLine. للقيام بذلك، نقوم بتحويل InputStream إلى BufferedReader، الذي يحتوي على طريقة readLine(). قد تؤدي القراءة إلى إلقاء استثناء.
8.3.6.5. إغلاق الاتصال
يتم ذلك باستخدام الطريقة:
قد تثير هذه الطريقة استثناءً. يتم تحرير الموارد المستخدمة، ولا سيما منفذ الشبكة.
8.3.6.6. بنية العميل
لدينا الآن العناصر اللازمة لوصف البنية الأساسية لعميل الإنترنت:
Socket sClient=null;
try{
// connect to the service running on port P of machine M
sClient=new Socket(M,P);
// create client socket I/O streams
BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);
// request-response loop
boolean fini=false;
String demande;
String réponse;
while (! fini){
// preparing the application
demande=…
// we send it
out.println(demande);
// we read the answer
réponse=in.readLine();
// the answer is processed
…
}
// it's over
sClient.close();
} catch(Exception e){
// we handle the exception
….
}
لم نحاول معالجة الأنواع المختلفة من الاستثناءات التي يولدها منشئ Socket أو الطرق readLine و getInputStream و getOutputStream و close من أجل الحفاظ على بساطة المثال. تم دمج كل شيء في استثناء واحد.
8.3.7. فئة ServerSocket
8.3.7.1. التعريف
هذه الفئة مخصصة لإدارة مآخذ التوصيل من جانب الخادم. نوضح هنا بعض منشئات وأساليب هذه الفئة:
ينشئ مأخذ توصيل في وضع الاستماع على المنفذ port | |
مثل ما سبق، ولكنه يضبط حجم قائمة الانتظار على count، أي الحد الأقصى لعدد اتصالات العملاء التي سيتم وضعها في قائمة الانتظار إذا كان الخادم مشغولاً عند وصول اتصال العميل. |
يعيد رقم منفذ الاستماع الذي يستخدمه المقبس | |
تُرجع عنوان InetAddress المحلي الذي يرتبط به المأخذ | |
يضع الخادم في حالة انتظار للاتصال (عملية حظر). عند وصول اتصال العميل، يعرض مأخذ توصيل يتم من خلاله تقديم الخدمة للعميل. | |
يغلق المأخذ وتدفقات الإدخال/الإخراج الخاصة به | |
تُرجع سلسلة "تمثل" المأخذ | |
يغلق مقبس الخدمة ويحرر الموارد المرتبطة به |
8.3.7.2. فتح الخدمة
يتم ذلك باستخدام المنشئين التاليين:
port هو منفذ الاستماع للخدمة: وهو المنفذ الذي يرسل إليه العملاء طلبات الاتصال الخاصة بهم. count هو الحجم الأقصى لقائمة انتظار الخدمة (50 بشكل افتراضي)، والتي تخزن طلبات اتصال العملاء التي لم يرد عليها الخادم بعد. عندما تمتلئ قائمة الانتظار، يتم رفض طلبات الاتصال الواردة. يقوم كلا المنشئين بإلقاء استثناء.
8.3.7.3. قبول طلب اتصال
عندما يرسل عميل طلب اتصال إلى منفذ الاستماع الخاص بالخدمة، تقبله الخدمة باستخدام الطريقة:
تُرجع هذه الطريقة مثيل Socket: وهو مأخذ توصيل الخدمة، الذي سيتم من خلاله تقديم الخدمة، غالبًا بواسطة مؤشر ترابط آخر. قد تُطلق الطريقة استثناءً.
8.3.7.4. القراءة/الكتابة عبر مأخذ توصيل الخدمة
نظرًا لأن مقبس الخدمة هو مثيل لفئة Socket، يرجى الرجوع إلى الأقسام السابقة التي تمت تغطية هذا الموضوع فيها.
8.3.7.5. تحديد العميل
بمجرد الحصول على مقبس الخدمة، يمكن تحديد العميل باستخدام الطريقة
الخاصة بفئة Socket. وهذا يتيح الوصول إلى عنوان IP واسم العميل.
8.3.7.6. إغلاق الخدمة
يتم ذلك باستخدام الطريقة
الخاصة بفئة ServerSocket. يؤدي ذلك إلى تحرير الموارد المستخدمة، ولا سيما منفذ الاستماع. قد ترمي هذه الطريقة استثناءً.
8.3.7.7. البنية الأساسية للخادم
بناءً على ما سبق، يمكننا كتابة الهيكل الأساسي للخادم:
SocketServer sEcoute=null;
try{
// ouverture du service
int portEcoute=…
int maxConnexions=…
sEcoute=new ServerSocket(portEcoute,maxConnexions);
// traitement des demandes de connexion
boolean fini=false;
Socket sService=null;
while( ! fini){
// attente et acceptation d'une demande
sService=sEcoute.accept();
// le service est rendu par une autre tâche à laquelle on passe la socket de service
new Service(sService).start();
// on se remet en attente des demandes de connexion
}
// c'est fini - on clôt le service
sEcoute.close();
} catch (Exception e){
// on traite l'exception
…
}
فئة Service هي مؤشر ترابط قد يبدو كما يلي:
public class Service extends Thread{
Socket sService; // service socket
// manufacturer
public Service(Socket S){
sService=S;
}
// run
public void run(){
try{
// create input-output flows
BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);
// request-response loop
boolean fini=false;
String demande;
String réponse;
while (! fini){
// we read the request
demande=in.readLine();
// we treat it
…
// we prepare the answer
réponse=…
// we send it
out.println(réponse);
}
// it's over
sService.close();
} catch(Exception e){
// we handle the exception
….
}// try
} // run
8.4. التطبيقات
8.4.1. خادم Echo
نقترح كتابة خادم إيكو يتم تشغيله من نافذة DOS باستخدام الأمر:
يعمل الخادم على المنفذ الذي تم تمريره كمعلمة. وهو ببساطة يعيد إرسال الطلب الذي أرسله العميل إليه، مع هوية العميل (IP+الاسم). ويقبل اتصالين في قائمة الانتظار الخاصة به. لدينا هنا جميع مكونات خادم TCP. البرنامج كالتالي:
// call: serveurEcho port
// echo server
// returns the line sent to the customer
import java.net.*;
import java.io.*;
public class serveurEcho{
public final static String syntaxe="Syntaxe : serveurEcho port";
public final static int nbConnexions=2;
// main program
public static void main (String arg[]){
// is there an argument
if(arg.length != 1)
erreur(syntaxe,1);
// this argument must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// create the listening socket
ServerSocket ecoute=null;
try{
ecoute=new ServerSocket(port,nbConnexions);
} catch (Exception e){
erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
}
// follow-up
System.out.println("Serveur d'écho lancé sur le port " + port);
// service loop
boolean serviceFini=false;
Socket service=null;
while (! serviceFini){
// waiting for a customer
try{
service=ecoute.accept();
} catch (IOException e){
erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
}
// we identify the link
try{
System.out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// the service is provided by another task
new traiteClientEcho(service).start();
}// end while
}// fine hand
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifies
private static String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// end class
// provides service to an echo server client
class traiteClientEcho extends Thread{
private Socket service; // service socket
private BufferedReader in; // iNPUTS
private PrintWriter out; // output flow
// manufacturer
public traiteClientEcho(Socket service){
this.service=service;
}
// run method
public void run(){
// creation of input and output flows
try{
in=new BufferedReader(new InputStreamReader(service.getInputStream()));
} catch (IOException e){
erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
}// fin try
try{
out=new PrintWriter(service.getOutputStream(),true);
} catch (IOException e){
erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
}// fin try
// link identification is sent to the customer
try{
out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// loop read request/write response
String demande,reponse;
try{
// the service stops when the client sends an end-of-file marker
while ((demande=in.readLine())!=null){
// echo of demand
reponse="["+demande+"]";
out.println(reponse);
// service stops when client sends "end
if(demande.trim().toLowerCase().equals("fin")) break;
}// end while
} catch (IOException e){
erreur("Erreur lors des échanges client/serveur ("+e+")",3);
}// fin try
// close the socket
try{
service.close();
} catch (IOException e){
erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
}// fin try
}// end run
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// end error
// identifies
private String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
تم دمج الفئتين المطلوبتين للخدمة في ملف مصدر واحد. واحدة فقط منهما، وهي التي تحتوي على الدالة الرئيسية، لها السمة العامة. تتوافق بنية الخادم مع البنية العامة لخوادم TCP. تمت إضافة طريقة (identify) لتحديد الاتصال بين الخادم والعميل. فيما يلي بعض النتائج:
يتم تشغيل الخادم بواسطة الأمر
ثم يعرض الرسالة التالية في نافذة وحدة التحكم:
لاختبار هذا الخادم، نستخدم برنامج telnet، المتوفر على كل من أنظمة Unix و Windows. Telnet هو عميل TCP عالمي مناسب لجميع الخوادم التي تقبل سطور نصية تنتهي بحرف نهاية السطر في اتصالاتها. وهذا هو الحال مع خادم echo الخاص بنا. نقوم بتشغيل أول عميل telnet على نظام Windows (2000 في هذا المثال) عن طريق كتابة telnet في نافذة DOS:
DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1
Le caractère d'escapement is 'CTRL+$'
Microsoft Telnet> help
Les commandes peuvent être abrégées. Les commandes prises en charge sont :
close ferme la connexion en cours
display affiche les paramètres d'operation
open ouvre une connexion à un site
quit quitte telnet
set définit les options (entrez 'set ?' to display the list)
status affiche les informations d'status
unset annule les options (entrez 'unset ?' to display the list)
? ou help affiche des informations d'help
Microsoft Telnet> set ?
NTLM Active l'authentication NTLM.
LOCAL_ECHO Active l'local echo.
TERM x (où x est ANSI, VT100, VT52 ou VTNT))
CRLF Envoi de CR et de LF
Microsoft Telnet> set local_echo
Microsoft Telnet> open localhost 187
بشكل افتراضي، لا يعرض برنامج Telnet الأوامر التي يتم كتابتها على لوحة المفاتيح. لتمكين هذه الميزة، أدخل الأمر:
لفتح اتصال بالخادم، مع تحديد منفذ خدمة الإعادة (187) وعنوان الجهاز الذي يعمل عليه (localhost)، أدخل الأمر التالي:
في نافذة DOS الخاصة بالعميل، ستتلقى بعد ذلك الرسالة التالية:
في نافذة الخادم، تظهر الرسالة التالية:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
هنا، يشير tahe و localhost إلى نفس الجهاز. في نافذة عميل Telnet، يمكنك كتابة أسطر من النص. يقوم الخادم بإعادة عرضها:
Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]
لاحظ أن منفذ العميل (1059) تم اكتشافه بشكل صحيح، لكن منفذ الخدمة (187) مطابق لمنفذ الاستماع (187)، وهو أمر غير متوقع. فمن المتوقع بالفعل الحصول على منفذ مقبس الخدمة بدلاً من منفذ الاستماع. يجب أن نتحقق مما إذا كنا سنحصل على نفس النتائج على نظام Unix. الآن، دعونا نطلق عميل telnet ثانٍ. تصبح نافذة الخادم كما يلي:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
في نافذة العميل الثاني، يمكنك أيضًا كتابة أسطر من النص:
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]
يوضح هذا أن خادم echo يمكنه خدمة عدة عملاء في نفس الوقت. يمكن إنهاء عمل عملاء Telnet عن طريق إغلاق نافذة DOS التي يعملون فيها.
8.4.2. عميل Java لخادم echo
في القسم السابق، استخدمنا عميل Telnet لاختبار خدمة الصدى. والآن سنكتب عميلنا الخاص:
// call: clientEcho machine port
// echo server client
// sends lines to the server, which echoes them back to the server
import java.net.*;
import java.io.*;
public class clientEcho{
public final static String syntaxe="Syntaxe : clientEcho machine port";
// main program
public static void main (String arg[]){
// are there two arguments
if(arg.length != 2)
erreur(syntaxe,1);
// the first argument must be the name of an existing machine
String machine=arg[0];
InetAddress serveurAddress=null;
try{
serveurAddress=InetAddress.getByName(machine);
} catch (Exception e){
erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
}
// port must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\nPort incorrect ("+E+")",3);
// connect to the server
Socket sClient=null;
try{
sClient=new Socket(machine,port);
} catch (Exception e){
erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
}
// we identify the link
try{
System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
+ "," + sClient.getPort() + "]");
} catch (Exception e) {
erreur("identification liaison ("+e+")",5);
}
// creation of a flow for reading lines typed on the keyboard
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
erreur("Création du flux d'entrée clavier ("+e+")",6);
}
// creation of the input stream associated with the client socket
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
} catch (Exception e){
erreur("Création du flux d'entrée de la socket client("+e+")",7);
}
// creation of the output stream associated with the client socket
PrintWriter out=null;
try{
out=new PrintWriter(sClient.getOutputStream(),true);
} catch (Exception e){
erreur("Création du flux de sortie de la socket ("+e+")",8);
}
// request-response loops
boolean serviceFini=false;
String demande=null;
String reponse=null;
// we read the message sent by the server just after connection
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// response display
System.out.println("Serveur : " +reponse);
while (! serviceFini){
// read a line typed on the keyboard
System.out.print("Client : ");
try{
demande=IN.readLine();
} catch (Exception e){
erreur("Lecture ligne ("+e+")",9);
}
// sending demand on the network
try{
out.println(demande);
} catch (Exception e){
erreur("Envoi demande ("+e+")",10);
}
// wait/read answer
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// response display
System.out.println("Serveur : " +reponse);
// is it over?
if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
}
// it's over
try{
sClient.close();
} catch(Exception e){
erreur("Fermeture socket ("+e+")",11);
}
}// hand
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifies
private static String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
يتوافق هيكل هذا العميل مع البنية العامة لعملاء TCP. هنا، قمنا بمعالجة الاستثناءات المختلفة المحتملة واحدة تلو الأخرى، مما يجعل البرنامج أثقل. فيما يلي النتائج التي تم الحصول عليها عند اختبار هذا العميل:
Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]
السطور التي تبدأ بـ "العميل" هي تلك التي أرسلها العميل، وتلك التي تبدأ بـ "الخادم" هي تلك التي ردها الخادم.
8.4.3. عميل TCP عام
تعمل العديد من الخدمات التي تم إنشاؤها في بدايات الإنترنت وفقًا لنموذج خادم الصدى الذي تمت مناقشته سابقًا: حيث يتكون الاتصال بين العميل والخادم من تبادل أسطر نصية. سنقوم بكتابة عميل TCP عام سيتم تشغيله على النحو التالي: java cltTCPgenerique server port
سيتصل عميل TCP هذا بالمنفذ port على الخادم server. وبمجرد الاتصال، سيقوم بإنشاء مؤشرين:
- خيط مسؤول عن قراءة الأوامر المكتوبة على لوحة المفاتيح وإرسالها إلى الخادم
- خيط مسؤول عن قراءة استجابات الخادم وعرضها على الشاشة
لماذا خيطان بينما لم يكن ذلك ضروريًا في التطبيق السابق؟ في ذلك التطبيق، كان بروتوكول الاتصال ثابتًا: أرسل العميل سطرًا واحدًا، ورد الخادم بسطر واحد. لكل خدمة بروتوكولها الخاص، ونواجه أيضًا الحالات التالية:
- يجب على العميل إرسال عدة أسطر من النص قبل تلقي الرد
- قد يحتوي رد الخادم على عدة أسطر من النص
لذلك، فإن الحلقة التي ترسل سطرًا واحدًا إلى الخادم وتستقبل سطرًا واحدًا من الخادم ليست مناسبة دائمًا. لذلك سننشئ حلقتين منفصلتين:
- حلقة لقراءة الأوامر المكتوبة على لوحة المفاتيح لإرسالها إلى الخادم. سيشير المستخدم إلى نهاية الأوامر باستخدام الكلمة الرئيسية "fin".
- حلقة لاستقبال وعرض ردود الخادم. ستكون هذه حلقة لا نهائية لن تتوقف إلا بإغلاق الخادم للاتصال بالشبكة أو بكتابة المستخدم للأمر "end" على لوحة المفاتيح.
للحصول على هاتين الحلقتين المنفصلتين، نحتاج إلى مؤشرين مستقلين. لنلقِ نظرة على مثال للتنفيذ حيث يتصل عميل TCP العام لدينا بخدمة SMTP (بروتوكول نقل البريد البسيط). هذه الخدمة مسؤولة عن توجيه البريد الإلكتروني إلى مستلميه. وهي تعمل على المنفذ 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: sender، لتحديد عنوان البريد الإلكتروني للمرسل
- rcpt to: recipient، لتحديد عنوان البريد الإلكتروني لمستلم الرسالة. إذا كان هناك عدة مستلمين، يتم تكرار الأمر rcpt to: عدة مرات حسب الحاجة لكل مستلم.
- data، الذي يشير إلى خادم SMTP بأن الرسالة على وشك الإرسال. كما هو موضح في استجابة الخادم، يتكون هذا من سلسلة من الأسطر تنتهي بسطر يحتوي على نقطة فقط. قد تحتوي الرسالة على رؤوس مفصولة عن نص الرسالة بسطر فارغ. في مثالنا، قمنا بتضمين موضوع باستخدام الكلمة الرئيسية Subject:
- بمجرد إرسال الرسالة، يمكننا إخبار الخادم بأننا انتهينا باستخدام الأمر quit. ثم يقوم الخادم بإغلاق اتصال الشبكة. يمكن لسلسلة القراءة اكتشاف هذا الحدث والتوقف.
- ثم يكتب المستخدم "end" على لوحة المفاتيح لإيقاف مؤشر الترابط الذي يقرأ الأوامر المكتوبة على لوحة المفاتيح أيضًا.
إذا تحققنا من البريد الإلكتروني المستلم، فسنرى ما يلي (Outlook):

لاحظ أن خدمة SMTP لا يمكنها تحديد ما إذا كان المرسل صالحًا أم لا. لذلك، لا يمكنك أبدًا الوثوق بحقل "من" في الرسالة. في هذه الحالة، لم يكن المرسل 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]
الأوامر الرئيسية هي كما يلي:
- تسجيل دخول المستخدم، حيث تقوم بإدخال معلومات تسجيل الدخول الخاصة بك على الجهاز الذي يستضيف بريدك الإلكتروني
- كلمة مرور، حيث تدخل كلمة المرور المرتبطة باسم المستخدم السابق
- list، للحصول على قائمة بالرسائل بالصيغة رقم، الحجم بالبايت
- retr i، لقراءة الرسالة رقم i
- الخروج، لإنهاء الجلسة.
دعونا الآن نستكشف بروتوكول الاتصال بين العميل وخادم الويب، الذي يعمل عادةً على المنفذ 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 من الخادم ويشير إلى أنه يستخدم الإصدار 1.0 من HTTP. أحدث إصدار من هذا البروتوكول هو 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 (بروتوكول نقل النص التشعبي). لن نتطرق إلى تفاصيل هذه الرؤوس هنا، ولكن ضع في اعتبارك أن عميلنا العام يوفر الوصول إليها، مما قد يساعد في فهمها. على سبيل المثال، السطر الأول:
يشير إلى أن خادم الويب الذي تم الاتصال به يدعم بروتوكول HTTP/1.1 وأنه عثر بنجاح على الملف المطلوب (200 OK)، حيث 200 هو رمز استجابة HTTP. الأسطر
يُعلم العميل بأنه سيتلقى 11,251 بايت من نص HTML (لغة ترميز النص التشعبي) وأن الاتصال سيُغلق بمجرد إرسال البيانات.
إذن، لدينا هنا عميل TCP مفيد للغاية. إنه بالتأكيد أقل قدرة من برنامج telnet الذي استخدمناه سابقًا، لكن كان من المثير للاهتمام أن نكتبه بأنفسنا. برنامج عميل TCP العام هو كما يلي:
// imported packages
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
// receives the characteristics of a service as a parameter in the form
// server port
// connects to the service
// creates a thread to read keyboard commands
// these will be sent to the
// creates a thread to read server responses
// these will be displayed on the screen
// the whole thing ends with the command end typed on the keyboard
// instance variable
private static Socket client;
public static void main(String[] args){
// syntax
final String syntaxe="pg serveur port";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the server name
String serveur=args[0];
// port must be integer >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;
// there may be problems
try{
// connect to the service
client=new Socket(serveur,port);
}catch(Exception ex){
// error
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
// end
return;
}//catch
// create read/write threads
new ClientSend(client).start();
new ClientReceive(client).start();
// end thread main
return;
}// hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
class ClientSend extends Thread {
// class for reading keyboard commands
// and send them to a server via a tcp client passed in parameter
private Socket client; // tcp client
// manufacturer
public ClientSend(Socket client){
// we note the tcp client
this.client=client;
}//manufacturer
// thread Run method
public void run(){
// local data
PrintWriter OUT=null; // network write streams
BufferedReader IN=null; // keyboard flow
String commande=null; // command read from keyboard
// error management
try{
// network write stream creation
OUT=new PrintWriter(client.getOutputStream(),true);
// keyboard input stream creation
IN=new BufferedReader(new InputStreamReader(System.in));
// order entry-send loop
System.out.println("Commandes : ");
while(true){
// read command typed on keyboard
commande=IN.readLine().trim();
// finished?
if (commande.toLowerCase().equals("fin")) break;
// send order to server
OUT.println(commande);
// next order
}//while
}catch(Exception ex){
// error
System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
OUT.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//run
}//class
class ClientReceive extends Thread{
// class responsible for reading lines of text intended for a
// tcp client passed as parameter
private Socket client; // tcp client
// manufacturer
public ClientReceive(Socket client){
// we note the tcp client
this.client=client;
}//manufacturer
// thread Run method
public void run(){
// local data
BufferedReader IN=null; // network read stream
String réponse=null; // server response
// error management
try{
// create network read stream
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// loop read text lines from IN stream
while(true){
// network streaming
réponse=IN.readLine();
// closed flow?
if(réponse==null) break;
// display
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// error
System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
IN.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//run
}//class
8.4.4. خادم TCP عام
الآن سنلقي نظرة على خادم
- يعرض على الشاشة الأوامر المرسلة من قبل عملائه
- ويرسل إليهم، رداً على ذلك، أسطر النص التي يكتبها المستخدم على لوحة المفاتيح. وبالتالي، فإن المستخدم هو الذي يقوم بدور الخادم.
يتم تشغيل البرنامج بواسطة: java genericTCPserver listeningPort، حيث listeningPort هو المنفذ الذي يجب على العملاء الاتصال به. سيتم التعامل مع خدمة العميل بواسطة خيطين:
- خيط مخصص حصريًا لقراءة أسطر النص المرسلة من العميل
- خيط مخصص حصريًا لقراءة الردود التي يكتبها المستخدم. سيشير هذا الخيط، باستخدام الأمر `fin`، إلى أنه يغلق الاتصال مع العميل.
يقوم الخادم بإنشاء خيطين لكل عميل. إذا كان هناك n عميل، فسيكون هناك 2n خيطًا نشطًا في نفس الوقت. لا يتوقف الخادم نفسه أبدًا ما لم يضغط المستخدم على Ctrl-C على لوحة المفاتيح. دعونا نلقي نظرة على بعض الأمثلة.
يعمل الخادم على المنفذ 100، ونستخدم العميل العام للتواصل معه. تبدو نافذة العميل كما يلي:
E:\data\serge\MSNET\c#\réseau\client tcp générique> 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
الآن دعونا نفتح متصفحًا ونطلب عنوان URL http://localhost:88/exemple.html. سيتصل المتصفح بعد ذلك بالمنفذ 88 على جهاز localhost ويطلب الصفحة /example.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. يغلق الأمر "end" الاتصال من الخادم إلى العميل. في ردنا، اقتصرنا على رؤوس 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 العام:
// packages
import java.io.*;
import java.net.*;
public class serveurTCPgenerique{
// main program
public static void main (String[] args){
// receives the port of listening to customer requests
// creates a thread to read client requests
// these will be displayed on the screen
// creates a thread to read keyboard commands
// these will be sent as a reply to the customer
// the whole thing ends with the command end typed on the keyboard
final String syntaxe="Syntaxe : pg port";
// instance variable
// is there an argument
if(args.length != 1)
erreur(syntaxe,1);
// port must be integer >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);
// we create the listening service
ServerSocket ecoute=null;
int nbClients=0; // no. of customers handled
try{
// create the service
ecoute=new ServerSocket(port);
// follow-up
System.out.println("Serveur générique lancé sur le port " + port);
// customer service loop
Socket client=null;
while (true){ // infinite loop - will be stopped by Ctrl-C
// waiting for a customer
client=ecoute.accept();
// the service is provided by separate threads
nbClients++;
// create read/write threads
new ServeurSend(client,nbClients).start();
new ServeurReceive(client,nbClients).start();
// back to listening to requests
}// end while
}catch(Exception ex){
// we report the error
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// fine hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
class ServeurSend extends Thread{
// class responsible for reading typed responses
// and send them to a client via a tcp client passed to the
Socket client; // tcp client
int numClient; // customer no
// manufacturer
public ServeurSend(Socket client, int numClient){
// we note the tcp client
this.client=client;
// and its
this.numClient=numClient;
}//manufacturer
// thread Run method
public void run(){
// local data
PrintWriter OUT=null; // network write streams
String réponse=null; // answer read from keyboard
BufferedReader IN=null; // keyboard flow
// follow-up
System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// error management
try{
// network write stream creation
OUT=new PrintWriter(client.getOutputStream(),true);
// keyboard flow creation
IN=new BufferedReader(new InputStreamReader(System.in));
// order entry-send loop
while(true){
// customer identification
System.out.print("--> " + numClient + " : ");
// read response typed on keyboard
réponse=IN.readLine().trim();
// finished?
if (réponse.toLowerCase().equals("fin")) break;
// send response to server
OUT.println(réponse);
// following response
}//while
}catch(Exception ex){
// error
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
OUT.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//run
}//class
class ServeurReceive extends Thread{
// class responsible for reading text lines sent to the server
// via a tcp client passed to the builder
Socket client; // tcp client
int numClient; // customer no
// manufacturer
public ServeurReceive(Socket client, int numClient){
// we note the tcp client
this.client=client;
// and its
this.numClient=numClient;
}//manufacturer
// thread Run method
public void run(){
// local data
BufferedReader IN=null; // network read stream
String réponse=null; // server response
// follow-up
System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// error management
try{
// create network read stream
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// loop read text lines from IN stream
while(true){
// network streaming
réponse=IN.readLine();
// closed flow?
if(réponse==null) break;
// display
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// error
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
IN.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//run
}//class
8.4.5. عميل ويب
في المثال السابق، رأينا بعض رؤوس HTTP التي أرسلها المتصفح:
<-- 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
<--
سنقوم بكتابة عميل ويب يأخذ عنوان URL كمعلمة ويعرض محتوى هذا العنوان على الشاشة. سنفترض أن خادم الويب الذي تم الاتصال به للحصول على عنوان URL يدعم بروتوكول HTTP 1.1. من الرؤوس المذكورة أعلاه، سنستخدم فقط ما يلي:
- يشير الرأس الأول إلى الصفحة التي نريدها
- والثاني يحدد الخادم الذي نستعلم عنه
- ويشير الثالث إلى أننا نريد من الخادم إغلاق الاتصال بعد الرد علينا.
إذا استبدلنا GET بـ HEAD في المثال أعلاه، فسيرسل لنا الخادم رؤوس HTTP فقط وليس صفحة HTML.
سيُطلق على عميل الويب الخاص بنا الاسم التالي: java clientweb URL cmd، حيث يمثل URL عنوان URL المطلوب، ويمثل cmd إحدى الكلمتين الرئيسيتين GET أو HEAD للإشارة إلى ما إذا كنا نريد الرؤوس فقط (HEAD) أم محتوى الصفحة أيضًا (GET). لنلقِ نظرة على المثال الأول. نبدأ بتشغيل خادم IIS ثم عميل الويب على نفس الجهاز:
dos>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
الاستجابة
يعني أن الصفحة المطلوبة قد تم نقلها (أي أن عنوان URL الخاص بها قد تغير). يتم توفير عنوان URL الجديد من خلال رأس Location:
إذا استخدمنا GET بدلاً من HEAD في استدعاء عميل الويب:
dos>java clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
نحصل على نفس النتيجة التي حصلنا عليها مع HEAD، بالإضافة إلى نص صفحة HTML. البرنامج كالتالي:
// imported packages
import java.io.*;
import java.net.*;
public class clientweb{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URI GET/HEAD";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the URI required
String URLString=args[0];
String commande=args[1].toUpperCase();
// URI validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// order verification
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// incorrect order
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// extract useful information from URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
try{
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// we read the answer
while((réponse=IN.readLine())!=null){
// the answer is processed
System.out.println(réponse);
}//while
// it's over
client.close();
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
الميزة الجديدة الوحيدة في هذا البرنامج هي استخدام فئة URL. يتلقى البرنامج عنوان URL (محدد موقع الموارد الموحد) أو URI (معرف الموارد الموحد) بالشكل http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... تسمح لنا فئة URL بتقسيم سلسلة URL إلى مكوناتها المختلفة. يتم إنشاء كائن URL من سلسلة URL المستلمة كمعلمة:
// vérification validité de l'URL
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
إذا كانت سلسلة URL المستلمة كمعلمة غير صالحة (بروتوكول مفقود، خادم، إلخ)، يتم إلقاء استثناء. وهذا يسمح لنا بالتحقق من صحة المعلمة المستلمة. بمجرد إنشاء كائن URL، يمكننا الوصول إلى عناصره المختلفة. وبالتالي، إذا تم إنشاء كائن URL من الكود السابق من السلسلة
فسيكون لدينا:
url.getHost() = server
url.getPort()=port أو -1 إذا لم يتم تحديد المنفذ
url.getPath()=HTMLPagePath أو سلسلة فارغة إذا لم يكن هناك مسار
url.getQuery() = param1=val1;param2=val2;... أو null إذا لم يكن هناك استعلام
uri.getProtocol() = http
8.4.6. معالجة عمليات إعادة التوجيه من قبل عميل الويب
لا يتعامل عميل الويب السابق مع أي إعادة توجيه لعنوان URL الذي طلبه. أما العميل التالي فيتعامل معها.
- يقرأ السطر الأول من رؤوس HTTP المرسلة من الخادم للتحقق مما إذا كان يحتوي على السلسلة "302 Object moved"، والتي تشير إلى إعادة توجيه
- يقرأ الرؤوس التالية. إذا كانت هناك إعادة توجيه، يبحث عن السطر "Location: url" الذي يوفر عنوان URL الجديد للصفحة المطلوبة ويسجل عنوان URL هذا.
- يعرض بقية استجابة الخادم. إذا كان هناك إعادة توجيه، تتكرر الخطوات من 1 إلى 3 باستخدام عنوان URL الجديد. لا يقبل البرنامج أكثر من إعادة توجيه واحدة. يتم تحديد هذا الحد بواسطة ثابت يمكن تعديله.
فيما يلي مثال:
Dos>java clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781
<html>
<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>
البرنامج كالتالي:
// imported packages
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class clientweb2{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URL GET/HEAD";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the URI required
String URLString=args[0];
String commande=args[1].toUpperCase();
// URI validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// order verification
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// incorrect order
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
final int nbRedirsMax=1; // no more than one redirection accepted
int nbRedirs=0; // number of redirects in progress
String premièreLigne; // 1st line of the answer
boolean redir=false; // indicates redirection or not
String locationString=""; // the URL string of a possible redirection
// regular expression to find a URL redirect
Pattern location=Pattern.compile("^Location: (.+?)$");
// error management
try{
// you may have several URL to request if there are redirections
while(nbRedirs<=nbRedirsMax){
// extract useful information from URL
String protocol=url.getProtocol();
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// read the first line of the answer
premièreLigne=IN.readLine();
// screen echo
System.out.println(premièreLigne);
// redirection?
if(premièreLigne.endsWith("302 Object moved")){
// there is a redirection
redir=true;
nbRedirs++;
}//if
// next HTTP headers until you find the empty line signalling the end of the headers
boolean locationFound=false;
while(!(réponse=IN.readLine()).equals("")){
// the answer is displayed
System.out.println(réponse);
// if there is a redirection, we search for the Location header
if(redir && ! locationFound){
// compare the line with the relational expression location
Matcher résultat=location.matcher(réponse);
if(résultat.find()){
// if found, note the URL of redirection
locationString=résultat.group(1);
// we note that we found
locationFound=true;
}//if
}//if
// next header
}//while
// following lines of the answer
System.out.println(réponse);
while((réponse=IN.readLine())!=null){
// the answer is displayed
System.out.println(réponse);
}//while
// close the connection
client.close();
// are we done?
if ( ! locationFound || nbRedirs>nbRedirsMax)
break;
// there is a redirection to be made - the new URL is built
URLString=protocol +"://"+host+":"+port+locationString;
url=new URL(URLString);
// follow-up
System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
}//while
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
8.4.7. خادم حساب الضرائب
نحن نعيد النظر في تمرين الضرائب، الذي تمت تغطيته بالفعل بأشكال مختلفة. دعونا نراجع أحدث نسخة:
تم إنشاء فئة أساسية باسم **impots**. سماتها هي ثلاثة مصفوفات من الأرقام:
public class impots{
// data required for tax calculation
// come from an external source
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// empty builder
protected impots(){}
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
تحتوي فئة impots على منشئين:
- منشئ يأخذ المصفوفات الثلاثة للبيانات اللازمة لحساب الضريبة
- منشئ بدون معلمات لا يمكن استخدامه إلا من قبل الفئات الفرعية
تم اشتقاق فئة **impotsJDBC** من هذه الفئة، مما يسمح بتعبئة المصفوفات الثلاثة limites* و*coeffR* وcoeffN* من محتويات قاعدة البيانات:
public class impotsJDBC extends impots{
// addition of a constructor for building
// limit tables, coeffr, coeffn from table
// database taxes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
تمت كتابة تطبيق رسومي. استخدم التطبيق كائنًا من فئة impotsJDBC. كان التطبيق وهذا الكائن موجودين على نفس الجهاز. نقترح وضع برنامج الاختبار وكائن impotsJDBC على أجهزة مختلفة. سيكون لدينا تطبيق خادم-عميل حيث يعمل كائن impotsJDBC البعيد كخادم. تسمى الفئة الجديدة TaxServer وهي مشتقة من فئة impotsJDBC:
// imported packages
import java.net.*;
import java.io.*;
import java.sql.*;
public class ServeurImpots extends impotsJDBC {
// attributes
int portEcoute; // the ability to listen to customer requests
boolean actif; // server status
// manufacturer
public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
throws IOException, SQLException, ClassNotFoundException {
// parent construction
super(DSNimpots, USERimpots, MDPimpots);
// we note the listening port
this.portEcoute=portEcoute;
// currently inactive
actif=false;
// creates and launches a thread for reading keyboard commands
// the server will be managed using these commands
Thread admin=new Thread(){
public void run(){
try{
admin();
}catch (Exception ignored){}
}
};
admin.start();
}//ServeurImpots
المعلمة الجديدة الوحيدة في المنشئ هي المنفذ المستخدم للاستماع لطلبات العميل. يتم تمرير المعلمات الأخرى مباشرة إلى الفئة الأساسية impotsJDBC. يتم التحكم في خادم الضرائب بواسطة الأوامر التي يتم إدخالها عبر لوحة المفاتيح. لذلك، نقوم بإنشاء مؤشر ترابط لقراءة هذه الأوامر. سيكون هناك أمران محتملان: start لتشغيل الخدمة، و stop لإيقافها نهائيًا. طريقة admin التي تتعامل مع هذه الأوامر هي كما يلي:
public void admin() throws IOException{
// reads server administration commands typed from the keyboard
// in an endless loop
String commande=null;
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
while(true){
// invite
System.out.print("Serveur d'impôts>");
// read command
commande=IN.readLine().trim().toLowerCase();
// order execution
if(commande.equals("start")){
// active?
if(actif){
//error
System.out.println("Le serveur est déjà actif");
// we continue
continue;
}//if
// create and launch the listening service
Thread ecoute=new Thread(){
public void run(){
ecoute();
}
};
ecoute.start();
}//if
else if(commande.equals("stop")){
// end of all execution threads
System.exit(0);
}//if
else {
// error
System.out.println("Commande incorrecte. Utilisez (start,stop)");
}//if
}//while
}//admin
إذا كان الأمر الذي تم إدخاله عبر لوحة المفاتيح هو start، يتم تشغيل مؤشر ترابط يستمع لطلبات العميل. إذا كان الأمر الذي تم إدخاله هو stop، يتم إيقاف جميع مؤشرات الترابط. يقوم مؤشر الترابط المستمع بتنفيذ طريقة listen:
public void ecoute(){
// thread for listening to customer requests
// we create the listening service
ServerSocket ecoute=null;
try{
// create the service
ecoute=new ServerSocket(portEcoute);
// follow-up
System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);
// service loop
Socket liaisonClient=null;
while (true){ // infinite loop
// waiting for a customer
liaisonClient=ecoute.accept();
// the service is provided by another task
new traiteClientImpots(liaisonClient,this).start();
// back to listening to requests
}// end while
}catch(Exception ex){
// we report the error
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}//listening thread
لدينا هنا خادم TCP كلاسيكي يستمع على المنفذ portEcoute. يتم التعامل مع طلبات العميل بواسطة طريقة run في مؤشر الترابط traiteCientImpots، والتي يتم تمرير معلمتين إليها عند الإنشاء:
- كائن Socket liaisonClient، الذي سيسمح بالوصول إلى العميل
- كائن impotsJDBC هذا، الذي يوفر الوصول إلى طريقة this.calculate لحساب الضريبة.
// -------------------------------------------------------
// provides service to a tax server client
class traiteClientImpots extends Thread{
private Socket liaisonClient; // customer liaison
private BufferedReader IN; // iNPUTS
private PrintWriter OUT; // output flow
private impotsJDBC objImpots; // object Tax
// manufacturer
public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
this.liaisonClient=liaisonClient;
this.objImpots=objImpots;
}//manufacturer
تقوم طريقة run بمعالجة طلبات العملاء. وهي عبارة عن أسطر نصية يمكن أن تتخذ شكلين:
- متزوج (نعم/لا) عدد_الأطفال الراتب_السنوي
- endCalculation
النموذج 1 يحسب الضريبة، بينما النموذج 2 يغلق اتصال العميل بالخادم.
// run method
public void run(){
// renders service to the customer
try{
// iNPUTS
IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
// output flow
OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
// send a welcome message to the customer
OUT.println("Bienvenue sur le serveur d'impôts");
// loop read request/write response
String demande=null;
String[] champs=null; // elements of the request
String commande=null; // customer order: calculation or fincalculs
while ((demande=IN.readLine())!=null){
// demand is broken down into fields
champs=demande.trim().toLowerCase().split("\\s+");
// two successful applications: calcul and fincalculs
commande=champs[0];
if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
// customer error
OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
// next order
continue;
}//if
if(commande.equals("calcul")) calculerImpôt(champs);
if(commande.equals("fincalculs")){
// good-bye message to customer
OUT.println("Au revoir...");
// freeing up resources
try{ OUT.close();IN.close();liaisonClient.close();}
catch(Exception ex){}
// end
return;
}//if
//following request
}//while
}catch (Exception e){
erreur("L'erreur suivante s'est produite ("+e+")",2);
}// fin try
}// end Run
يتم حساب الضريبة بواسطة طريقة calculateTax، التي تأخذ مصفوفة الحقول من طلب العميل كمعلمة. يتم التحقق من صحة الطلب، وإذا كان صحيحًا، يتم حساب الضريبة وإرجاعها إلى العميل.
// tax calculation
public void calculerImpôt(String[] champs){
// processing the application: calculation married nbEnfants salaireAnnuel
// broken down into fields in the fields table
String marié=null;
int nbEnfants=0;
int salaireAnnuel=0;
// validity of arguments
try{
// at least 4 fields are required
if(champs.length!=4) throw new Exception();
// married
marié=champs[1];
if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
// children
nbEnfants=Integer.parseInt(champs[2]);
// salary
salaireAnnuel=Integer.parseInt(champs[3]);
}catch (Exception ignored){
// format error
OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
// finish
return;
}//if
// tax can be calculated
long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
// we send the response to the customer
OUT.println(""+impot);
}//calculate
قد يبدو برنامج الاختبار كما يلي:
// call: serveurImpots port dsnImpots userImpots mdpImpots
import java.io.*;
public class testServeurImpots{
public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";
// main program
public static void main (String[] args){
// you need 4 arguments
if(args.length != 4)
erreur(syntaxe,1);
// port must be integer >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);
// we create the tax server
try{
new ServeurImpots(port,args[1],args[2],args[3]);
}catch(Exception ex){
//error
System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
}//catch
}//Main
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}// end class
نقوم بتمرير البيانات اللازمة لإنشاء كائن TaxServer إلى برنامج الاختبار، ومن هناك يقوم البرنامج بإنشاء هذا الكائن.
دعونا نجرب تشغيله للمرة الأولى:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
الأمر
يُنشئ كائن TaxServer الذي لا يستمع بعد لطلبات العملاء. إن الأمر start الذي يتم كتابته على لوحة المفاتيح هو الذي يبدأ عملية الاستماع هذه. أما الأمر stop فيقوم بإيقاف تشغيل الخادم. لنستخدم الآن عميلاً. سنستخدم العميل العام الذي تم إنشاؤه سابقاً. الخادم قيد التشغيل:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
يتم تشغيل العميل العام في نافذة DOS أخرى:
يمكننا أن نرى أن العميل قد تلقى رسالة الترحيب من الخادم بنجاح. نرسل أوامر أخرى:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
نعود إلى نافذة الخادم لإيقافه:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
8.5. تمارين
8.5.1. التمرين 1 - الرسم البياني العام لعميل TCP
8.5.1.1. نظرة عامة على التطبيق
نقترح إنشاء برنامج قادر على التواصل عبر الإنترنت مع خدمات TCP الرئيسية. سنسميه عميل TCP عام. بمجرد فهمك لهذا التطبيق، ستلاحظ أن جميع عملاء TCP متشابهون. تبدو نافذة البرنامج كما يلي:

فيما يلي معاني عناصر التحكم المختلفة:
رقم | الاسم | النوع | الدور |
1 | TxtRemoteHost | JTextField | اسم الجهاز الذي يقدم الخدمة المطلوبة |
2 | TxtPort | JTextField | منفذ الخدمة المطلوبة |
3 | TxtSend | JTextField | نص الرسالة التي سيقوم العميل بإرسالها إلى الخادم |
4 | OptRCLF OptLF | JCheckBox | الأزرار المستخدمة لتحديد كيفية إنهاء الأسطر في حوار العميل/الخادم RCLF: إرجاع الحامل (#13) + تغذية السطر (#10) LF: تغذية السطر (#10) |
5 | LstSuivi | JList | يعرض رسائل حول حالة الاتصال بين العميل والخادم |
6 | LstDialogue | JList | يعرض الرسائل المتبادلة بين العميل (->) والخادم (<-) |
7 | CmdCancel | JButton | مخفي - يقع أسفل قائمة الحوار - يظهر عندما يكون الاتصال قيد التقدم ويسمح لك بإنهائه إذا لم يستجب الخادم |
خيارات القائمة المتاحة هي كما يلي:
الخيار | الخيارات الفرعية | الدور |
الاتصال | اتصال | يربط العميل بالخادم |
قطع الاتصال | يغلق الاتصال | |
خروج | يخرج من البرنامج | |
الرسائل | إرسال | يرسل الرسالة من عنصر التحكم TxtSend إلى الخادم |
ClearTracking | مسح قائمة LstSuivi | |
ClearDialogue | مسح قائمة LstDialogue | |
المؤلف | يعرض مربع حقوق النشر |
8.5.1.2. كيفية عمل التطبيق
عند تحميل النافذة الرئيسية للتطبيق، تحدث الإجراءات التالية:
- يتم توسيط الورقة على الشاشة
- يتم تنشيط خيارات القائمة "تسجيل الدخول/تسجيل الخروج" و"المؤلف" فقط
- يتم إخفاء زر "إلغاء"
- تكون قائمتا LstSuivi و LstDialogue فارغتين
هذا الخيار متاح فقط عندما لا تكون حقول المضيف البعيد ورقم المنفذ فارغة ولا يوجد اتصال نشط حاليًا. يؤدي النقر على هذا الخيار إلى تنفيذ العمليات التالية:
- يتم التحقق من صحة المنفذ: يجب أن يكون عددًا صحيحًا أكبر من 0
- يتم تشغيل مؤشر ترابط لإنشاء الاتصال بالخادم
- يظهر زر "إلغاء" للسماح للمستخدم بمقاطعة الاتصال الجاري
- يتم تعطيل جميع خيارات القائمة باستثناء "الخروج" و"المؤلف"
يمكن أن ينتهي الاتصال بعدة طرق:
- نقر المستخدم على زر "إلغاء": يتم إيقاف مؤشر ترابط الاتصال، وتعود القائمة إلى حالتها الأولية. يشير السجل إلى أن المستخدم أغلق الاتصال.
- ينتهي الاتصال بحدوث خطأ: نقوم بنفس الإجراء السابق، بالإضافة إلى ذلك، نشير في السجل إلى سبب الخطأ.
- اكتمل الاتصال بنجاح: يتم إزالة زر "إلغاء"، ويشير السجل إلى أن الاتصال قد تم، ويتم تمكين قائمة "إعادة تعيين السجل"، وتعطيل قائمة "الاتصال"، وتمكين قائمة "قطع الاتصال"
لا يتوفر هذا الخيار إلا في حالة وجود اتصال بالخادم. وعند تفعيله، فإنه يغلق الاتصال بالخادم ويعيد القائمة إلى حالتها الأولية. ويشير السجل إلى أن العميل هو الذي أغلق الاتصال.
يغلق هذا الخيار أي اتصال نشط بالخادم ويخرج من التطبيق.
هذا الخيار متاح فقط في حالة استيفاء الشروط التالية:
-
تم إنشاء اتصال بالخادم
-
وجود رسالة لإرسالها
إذا تم استيفاء هذه الشروط، يتم إرسال النص الموجود في حقل TxtSend (3) إلى الخادم، مع إنهاءه بتسلسل RCLF إذا تم تحديد خيار RCLF، أو بتسلسل LF في الحالات الأخرى. يتم الإبلاغ عن أي أخطاء في الإرسال في قائمة التتبع.
مسح قوائم LstSuivi و LstDialogue على التوالي. يتم تعطيل هذه الخيارات عندما تكون القوائم المقابلة فارغة.
يظهر هذا الزر، الموجود في أسفل النموذج، فقط عندما يحاول العميل الاتصال بالخادم. قد يفشل هذا الاتصال لأن الخادم لا يستجيب أو يستجيب بشكل غير صحيح. يتيح زر "إلغاء" للمستخدم عندئذ خيار إلغاء طلب الاتصال.
تتتبع قائمة LstSuivi (5) عملية الاتصال. وهي تشير إلى اللحظات الرئيسية في عملية الاتصال:
-
إنشاؤه بواسطة العميل
-
إغلاقه من قبل الخادم أو العميل
-
أي أخطاء قد تحدث أثناء نشاط الاتصال
تتتبع قائمة LstDialogue (6) الحوار الذي تم إقامته بين العميل والخادم. يراقب مؤشر ترابط في الخلفية ما يحدث على مقبس اتصال العميل ويعرضه في القائمة 6.
تفتح هذه القائمة نافذة تسمى حقوق النشر:

يتم الإبلاغ عن أخطاء الاتصال في قائمة التتبع 6، بينما يتم الإبلاغ عن الأخطاء المتعلقة بحوار العميل/الخادم في قائمة الحوار 7. في حالة حدوث خطأ في الاتصال، يتم إغلاق حوار العميل/الخادم وإعادة تعيين النموذج إلى حالته الأولية، ليكون جاهزًا لاتصال جديد.
8.5.1.3. المهام
تنفيذ العمل الموصوف أعلاه في شكلين:
- تطبيق مستقل
- برنامج صغير
8.5.2. التمرين 2 - خادم الموارد
8.5.2.1. مقدمة
تمتلك إحدى المؤسسات عدة خوادم حوسبة قوية يمكن الوصول إليها عبر الإنترنت. أي جهاز يرغب في استخدام خدمات الحوسبة هذه يرسل ملف بيانات إلى المنفذ 756 على أحد الخوادم. يحتوي هذا الملف على معلومات متنوعة: اسم المستخدم، وكلمة المرور، والأوامر التي تحدد نوع الحساب المطلوب، والبيانات التي سيتم إجراء الحساب عليها. إذا كان ملف البيانات صالحًا، يقوم خادم الحوسبة المحدد بمعالجته وإرجاع النتائج إلى العميل في شكل ملف نصي.
تتعدد مزايا هذا الإعداد:
- يمكن لأي نوع من العملاء (PC، Mac، Unix، إلخ) استخدام هذه الخدمة
- يمكن أن يكون العميل في أي مكان على الإنترنت
- يتم تحسين موارد الحوسبة: لا يتطلب الأمر سوى عدد قليل من الأجهزة القوية. وبالتالي، يمكن لمنظمة صغيرة تفتقر إلى موارد الحوسبة استخدام هذه الخدمة مقابل رسوم تُحسب بناءً على وقت الحوسبة المستخدم.
على الرغم من قوة الأجهزة، قد يستغرق الحساب أحيانًا عدة ساعات: وبالتالي يصبح الخادم غير متاح للعملاء الآخرين. وهذا يطرح مشكلة للعميل في العثور على خادم حوسبة متاح. لمعالجة ذلك، يتم استخدام "مدير موارد الحوسبة"، المشار إليه فيما يلي باسم خادم GRC. تعمل هذه الخدمة على جهاز واحد وتعمل على المنفذ 864 في وضع TCP. يتصل العميل الذي يسعى للوصول إلى خادم حوسبة بهذه الخدمة. يستجيب خادم GRC، الذي يحتفظ بقائمة كاملة بخوادم الحوسبة، بإرسال اسم خادم خامل حاليًا إلى العميل. ثم يقوم العميل ببساطة بإرسال بياناته إلى الخادم المحدد.
نقترح تطوير خادم GRC.
8.5.2.2. الواجهة المرئية
ستكون الواجهة المرئية على النحو التالي:

تعرض الواجهة قائمتين من الخوادم:
- على اليسار، قائمة الخوادم غير النشطة، والتي تكون بالتالي متاحة لإجراء العمليات الحسابية
- على اليمين، قائمة الخوادم المشغولة بحسابات أحد العملاء.
هيكل القائمة كما يلي:
القائمة الرئيسية | القائمة الفرعية | الدور |
الخدمة | بدء | بدء تشغيل خدمة TCP على المنفذ 864 |
إيقاف | إيقاف الخدمة | |
خروج | الخروج من التطبيق | |
المؤلف | معلومات حقوق النشر |
هيكل عناصر التحكم في النموذج كما يلي:
الاسم | النوع | الدور |
listLibres | JList | قائمة الخوادم المتاحة |
listBusy | JList | قائمة الخوادم المشغولة |
8.5.2.3. كيف يعمل التطبيق
عند تحميل التطبيق، يتم ملء قائمة listLibres بقائمة أسماء خوادم الحوسبة التي تديرها GRC. يتم تعريف هذه الخوادم في ملف Servers يتم تمريره كمعلمة. يحتوي هذا الملف على قائمة بأسماء الخوادم، اسم واحد في كل سطر، وبالتالي يتم استخدامه لملء قائمة listLibres. يتم تمكين قائمة Start (بدء)؛ ويتم تعطيل قائمة Stop (إيقاف).
يؤدي هذا الخيار
- يبدأ خدمة الاستماع على المنفذ 864 للجهاز
- يعطل قائمة "ابدأ"
- يقوم بتمكين قائمة الإيقاف
يوقف هذا الخيار الخدمة:
- يتم مسح قائمة الخوادم المشغولة
- يتم ملء قائمة الخوادم المتاحة بمحتويات ملف الخوادم
- يتم تمكين قائمة "ابدأ"
- يتم تعطيل قائمة "إيقاف"
يتم إنهاء التطبيق.
يتم إجراء الحوار بين العميل والخادم عبر تبادل أسطر نصية تنتهي بتسلسل RCLF. يتعرف خادم GRC على أمرين: getserver و endservice. نوضح دور هذين الأمرين بالتفصيل:
- 1-getserver
يسأل العميل عما إذا كان خادم الحوسبة متاحًا له.
ثم يأخذ خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> أول خادم موجود في قائمة الخوادم المتاحة لديه ويعيد اسمه إلى العميل بالصيغة التالية:
بالإضافة إلى ذلك، فإنه ينقل الخادم المخصص للعميل إلى قائمة الخوادم المشغولة بالصيغة التالية:
كما هو موضح في المثال التالي، حيث يكون الخادم *<bdi dir="ltr" class="odt-ltr-term">calcul1.istia.univ-angers.fr</bdi>* مشغولاً بخدمة العميل الذي يحمل عنوان <bdi dir="ltr" class="odt-ltr-term">IP</bdi> *193.52.43.5*:

لا يمكن للعميل إرسال أمر **<bdi dir="ltr" class="odt-ltr-term">getserver</bdi>** إذا كان قد تم تخصيص خادم حوسبة له بالفعل. لذلك، قبل الرد على العميل، يتحقق خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> من أن عنوان <bdi dir="ltr" class="odt-ltr-term">IP</bdi> الخاص بالعميل غير موجود بالفعل ضمن العناوين المسجلة في قائمة الخوادم المشغولة. إذا كان الأمر كذلك، يرد خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> بما يلي:
أخيرًا، هناك حالة لا يتوفر فيها أي خادم حوسبة: قائمة الخوادم المتاحة فارغة. في هذه الحالة، يرد خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> بما يلي:
في جميع الحالات، بعد الرد على العميل، يقوم خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> بإغلاق الاتصال مع العميل حتى يتمكن من خدمة عملاء آخرين.
- 2-finservice
يشير العميل إلى أنه لم يعد بحاجة إلى خادم الحوسبة الذي كان يستخدمه.
يقوم خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> أولاً بالتحقق من أن العميل هو بالفعل أحد العملاء الذين كان يخدمهم. وللقيام بذلك، يتحقق من ما إذا كان عنوان <bdi dir="ltr" class="odt-ltr-term">IP</bdi> الخاص بالعميل موجوداً ضمن العناوين المسجلة في قائمة الخوادم المشغولة. وإذا لم يكن الأمر كذلك، يرد خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> بما يلي:
إذا تم التعرف على العميل، يرد خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> بما يلي:
وينقل خادم الحوسبة المخصص لهذا العميل إلى قائمة الخوادم المتاحة. للعودة إلى المثال السابق، إذا أرسل العميل أمر **إنهاء** الخدمة، يصبح عرض خادم <bdi dir="ltr" class="odt-ltr-term">GRC</bdi> كما يلي:

بعد إرسال الرد، بغض النظر عن محتواه، يقوم خادم GRC بإغلاق الاتصال.
8.5.2.4. المهمة
اكتب التطبيق كبرنامج مستقل يمكن اختباره، على سبيل المثال، باستخدام عميل telnet أو باستخدام عميل TCP العام من التمرين السابق.
8.5.3. التمرين 3 - عميل SMTP
8.5.3.1. مقدمة
هنا، نريد إنشاء عميل لخدمة SMTP (بروتوكول نقل البريد البسيط)، الذي يسمح لك بإرسال البريد الإلكتروني. في أنظمة Unix أو Windows، يعد برنامج telnet عميلاً يعمل مع بروتوكول TCP. ويمكنه "التواصل" مع أي خدمة TCP تقبل الأوامر النصية التي تنتهي بتسلسل RCLF، أي الأحرف ASCII 13 و10. وفيما يلي مثال على محادثة مع خدمة SMTP لإرسال البريد الإلكتروني:
$ telnet istia.univ-angers.fr 25 // appel du service smtp
// الرد الوارد من خادم SMTP
Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here
// تعليقات --------------
يمكن لبرنامج telnet الاتصال بأي خدمة باستخدام الصيغة
***<bdi dir="ltr" class="odt-ltr-term">telnet</bdi>*** **<bdi dir="ltr" class="odt-ltr-term">machine</bdi>\_service <bdi dir="ltr" class="odt-ltr-term">port</bdi>\_service**
تستخدم تبادلات العميل/الخادم أسطر نصية تنتهي بتسلسل RCLF.
تكون الردود من خدمة SMTP بالشكل التالي:
**رقم الرسالة أو**
**رقم الرسالة**
قد يرسل خادم SMTP عدة أسطر من الردود. يُشار إلى السطر الأخير من الرد برقم متبوع بمسافة، بينما يُشار إلى الأسطر السابقة من الرد برقم متبوع بشرطة -.
يشير الرقم الأكبر من أو يساوي 500 إلى رسالة خطأ.
// نهاية التعليقات
// استجابة من خادم SMTP
214-Commands:
214- HELO EHLO MAIL RCPT DATA
214- RSET NOOP QUIT HELP VRFY
214- EXPN VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214- sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
mail from: serge.tahe@istia.univ-angers.fr // nouvelle commande émise au clavier
// تعليقات ---------
يحتوي الأمر mail على الصيغة التالية:
**<bdi dir="ltr" class="odt-ltr-term">mail from:</bdi> عنوان البريد الإلكتروني لمرسل الرسالة**
// نهاية التعليقات
// الرد من خادم SMTP
// تعليقات
لا يتحقق خادم SMTP من صحة عنوان المرسل: فهو يقبله كما هو
// نهاية التعليقات
rcpt to: user1@istia.univ-angers.fr // nouvelle commande émise au clavier
// تعليقات ---------
يتميز الأمر rcpt بالصيغة التالية:
**<bdi dir="ltr" class="odt-ltr-term">rcpt to:</bdi> عنوان البريد الإلكتروني لمستلم الرسالة**
إذا كان عنوان البريد الإلكتروني موجودًا على الجهاز الذي يعمل عليه خادم SMTP، فإنه يتحقق من وجوده؛ وإلا، فإنه لا يقوم بأي تحقق. إذا تم إجراء التحقق وتم الكشف عن خطأ، فسيتم الإبلاغ عنه برقم >= 500.
يمكنك إصدار عدد غير محدود من أوامر rcpt: وهذا يتيح لك إرسال رسالة إلى عدة أشخاص.
// نهاية التعليقات
// استجابة خادم SMTP
// تعليقات ---------
يحتوي الأمر data على الصيغة التالية:
**<bdi dir="ltr" class="odt-ltr-term">data</bdi>**
**السطر 1**
**السطر 2**
**...**
**.**
ويلي ذلك أسطر النص التي تشكل الرسالة، والتي يجب أن تنتهي بسطر يحتوي فقط على حرف "نقطة".
ثم يتم إرسال الرسالة إلى المستلم المحدد بواسطة الأمر rcpt.
// نهاية التعليقات
// استجابة من خادم SMTP
// نص الرسالة المكتوب على لوحة المفاتيح
subject: essai smtp
essai smtp a partir de telnet
.
// تعليقات
في الأسطر النصية لأمر data، يمكنك تضمين سطر subject: لتحديد موضوع البريد الإلكتروني. يجب أن يتبع هذا السطر سطر فارغ.
// الرد من خادم SMTP
// تعليقات
**يغلق الأمر <bdi dir="ltr" class="odt-ltr-term">quit</bdi> الاتصال بخدمة** ***<bdi dir="ltr" class="odt-ltr-term">SMTP</bdi>***
// نهاية التعليقات
// استجابة من خادم SMTP
8.5.3.2. الواجهة المرئية
نقترح إنشاء برنامج بالواجهة المرئية التالية:

تتمتع عناصر التحكم بالوظائف التالية:
الرقم | النوع | الدور |
1 | JTextField | قائمة بعناوين البريد الإلكتروني مفصولة بفواصل |
2 | JTextField | نص موضوع الرسالة |
3 | JTextField | قائمة عناوين البريد الإلكتروني مفصولة بفواصل |
4 | JTextField | قائمة عناوين البريد الإلكتروني مفصولة بفواصل |
5 | JTextArea | نص الرسالة |
6 | JList | قائمة المتابعة |
7 | JList | قائمة الحوار |
8 | JButton | زر "إلغاء" غير ظاهر، يظهر عندما يطلب العميل الاتصال بخادم SMTP. يسمح للمستخدم بإلغاء هذا الطلب إذا لم يستجب الخادم. |
8.5.3.3. القوائم
هيكل قائمة التطبيق كما يلي:
القائمة الرئيسية | القائمة الفرعية | الدور |
البريد | ||
إرسال | إرسال الرسالة من عنصر التحكم 5 | |
خروج | الخروج من التطبيق | |
خيارات | ||
إخفاء التتبع | إخفاء عنصر التحكم 6 | |
مسح قائمة المراقبة | مسح قائمة المراقبة 6 | |
إخفاء مربع الحوار | إخفاء قائمة الحوار 7 | |
مسح قائمة الحوار | مسح قائمة الحوار 7 | |
تكوين | يسمح للمستخدم بتحديد - عنوان خادم SMTP الذي يستخدمه البرنامج - عنوان بريده الإلكتروني | |
حفظ... | يحفظ التكوين السابق في ملف .ini | |
المؤلف | معلومات حقوق النشر |
8.5.3.4. كيفية عمل التطبيق
تعرض هذه القائمة النافذة التالية:

يجب ملء كلا الحقلين حتى يصبح زر "موافق" نشطًا. يجب تخزين كلتا المعلومتين في متغيرات عامة حتى تكونا متاحتين للوحدات النمطية الأخرى.
لا يتوفر هذا الخيار إلا في حالة استيفاء الشروط التالية:
- تم الانتهاء من التكوين
- وجود رسالة لإرسالها
- وجود موضوع
- وجود مستلم واحد على الأقل في الحقول 1 و3 و4
إذا تم استيفاء هذه الشروط، فإن تسلسل الأحداث يكون كما يلي:
- يتم تعيين النموذج إلى حالة يتم فيها تعطيل جميع الإجراءات التي قد تتداخل مع الحوار بين العميل والخادم
- يتم إنشاء اتصال على المنفذ 25 للخادم المحدد في التكوين
- ثم يتواصل العميل مع خادم SMTP وفقًا للبروتوكول الموصوف أعلاه
- يستخدم حقل "Mail From" عنوان البريد الإلكتروني للمرسل المحدد في التكوين
- يُستخدم الأمر "rcpt to:" لكل عنوان بريد إلكتروني موجود في الحقول 1 و3 و4
- في الأسطر المرسلة بعد أمر البيانات، سيظهر النص التالي:
- سطر "Subject": نص الموضوع من عنصر التحكم 2
- a سطر Cc:: عناوين من الاختبار 3
- a سطر "Bcc": عناوين من الاختبار 4
- نص الرسالة للتحكم 5
- نقطة النهاية
يظهر هذا الزر الموجود أسفل النموذج فقط عندما يحاول العميل الاتصال بخادم SMTP. قد يفشل هذا الاتصال لأن خادم SMTP لا يستجيب أو يستجيب بشكل غير صحيح. ويتيح زر "إلغاء" للمستخدم عندئذٍ إلغاء طلب الاتصال.
تتتبع القائمة (6) عملية الاتصال. وهي تشير إلى اللحظات الرئيسية في عملية الاتصال:
- إنشاء الاتصال من قبل العميل
- إغلاقه من قبل الخادم أو العميل
- جميع أخطاء الاتصال
تتتبع القائمة (7) الحوار SMTP الذي تم إقامته بين العميل والخادم.
ترتبط هاتان القائمتان بخيارات القائمة:
إخفاء التتبع | يخفي قائمة التتبع 6 والتسمية الموجودة فوقها. إذا كان الارتفاع الذي تشغله هذين العنصرين هو H، يتم تحريك جميع العناصر الموجودة أسفلهما لأعلى بمقدار الارتفاع H، ويتم تقليل الحجم الإجمالي للنموذج بمقدار H. بالإضافة إلى ذلك، يخفي خيار إخفاء التتبع خيار مسح التتبع الموجود أسفله. |
مسح التتبع | يمسح قائمة التتبع 6 |
إخفاء مربع الحوار | يخفي قائمة الحوار 7، والتسمية الموجودة فوقها، وخيار القائمة "مسح الحوار" الموجود أسفلها. كما هو الحال مع "إخفاء التتبع"، يتم إعادة حساب موضع عناصر التحكم الموجودة أسفلها (ربما زر "إلغاء")، ويتم تقليل حجم النافذة. |
مسح الحوار | مسح قائمة الحوار 7 |
تفتح هذه القائمة نافذة تسمى حقوق النشر:

يتم الإبلاغ عن أخطاء الاتصال في قائمة التتبع 6، بينما يتم الإبلاغ عن الأخطاء المتعلقة بحوار العميل/الخادم في قائمة الحوار 7. عند حدوث خطأ، يتم إخطار المستخدم عبر مربع خطأ، ويتم عرض القائمة التي تحتوي على سبب الخطأ إذا كانت مخفية مسبقًا. بالإضافة إلى ذلك، يتم إغلاق حوار العميل/الخادم، وتتم إعادة النموذج إلى حالته الأولية.
8.5.3.5. إدارة ملف التكوين
من المستحسن ألا يضطر المستخدم إلى إعادة تكوين البرنامج في كل مرة يستخدمه. لتحقيق ذلك، إذا تم تحديد خيار "خيارات/حفظ التكوين عند الخروج"، فإن إغلاق البرنامج يحفظ معلومتين تم تعيينهما عبر خيار "خيارات/تكوين"، بالإضافة إلى حالة قائمتي التتبع، في ملف sendmail.ini الموجود في نفس الدليل الذي يوجد فيه ملف .exe الخاص بالبرنامج. هذا الملف له التنسيق التالي:
SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1
تحتوي سطور SmtpServer و ReplyAddress على معلومتين تم إدخالهما عبر قائمة Options/Configure. تشير سطور Tracking و Dialogue إلى حالة قوائم التتبع والحوار: 1 (موجود)، 0 (غير موجود).
عند تحميل البرنامج، يتم قراءة ملف sendmail.ini إذا كان موجودًا، ويتم تكوين النموذج وفقًا لذلك. إذا لم يكن ملف sendmail.ini موجودًا، يتصرف البرنامج كما لو كان موجودًا:
إذا كان ملف sendmail.ini موجودًا ولكنه غير مكتمل (توجد أسطر مفقودة)، يتم استبدال السطر المفقود بالسطر المقابل أعلاه. وبالتالي، إذا كان السطر Suivi=... مفقودًا، فإننا نتعامل معه كما لو كان لدينا Suivi=1.
جميع الأسطر التي لا تتطابق مع النمط:
يتم تجاهلها، وكذلك تلك التي تكون فيها الكلمة الرئيسية غير صالحة. يمكن أن تكون الكلمة الرئيسية بأحرف كبيرة أو صغيرة: لا يهم.
في قائمة "خيارات/تكوين"، يتم عرض قيمتي SmtpServer و ReplyAddress الحاليتين. يمكن للمستخدم تعديلهما إذا رغب في ذلك.
8.5.3.6. المهام المطلوب تنفيذها
أكمل المهمة الموضحة أعلاه. يوصى بالتعامل مع إدارة ملفات التكوين في النهاية.
8.5.4. التمرين 4 - عميل POPPASS
8.5.4.1. مقدمة
نقترح إنشاء عميل TCP قادر على التواصل مع خادم POPPASSD الذي يعمل على المنفذ 106. تتيح لك هذه الخدمة تغيير كلمة مرورك على جهاز UNIX. بروتوكول الاتصال بين العميل والخادم هو كما يلي:
1 - يتم الاتصال عبر تبادل الرسائل التي تنتهي بتسلسل RCLF
2 - يرسل العميل الأوامر إلى الخادم
- يرد الخادم برسائل تبدأ برقم مكون من 3 أرقام: <bdi dir="ltr" class="odt-ltr-term">XXX.</bdi> إذا كان <bdi dir="ltr" class="odt-ltr-term">XXX</bdi>=200، فهذا يعني أن الأمر قد تم تنفيذه بنجاح؛ وإلا، فقد حدث خطأ.
3 - تسلسل التبادلات كما يلي:
- يرد الخادم برسالة ترحيب
- يستجيب الخادم بطلب كلمة المرور إذا تم قبول اسم المستخدم؛ وإلا، فإنه يعرض رسالة خطأ
- يستجيب الخادم بطلب كلمة المرور الجديدة إذا تم قبول كلمة المرور؛ وإلا، فإنه يعرض رسالة خطأ
- يستجيب الخادم بتأكيد قبول كلمة المرور الجديدة؛ وإلا، فإنه يعرض رسالة خطأ
- يرسل الخادم رسالة إنهاء ويغلق الاتصال
8.5.4.2. نموذج العميل

فيما يلي معنى العناصر المختلفة:
رقم | الاسم | النوع | الدور |
1 | txtRemoteHost | JTextField | اسم الخادم |
2 | txtLogin | JTextField | تسجيل دخول المستخدم |
3 | txtPassword | JTextField | كلمة مرور المستخدم |
4 | txtNewPassword | JTextField | كلمة مرور المستخدم الجديدة |
5 | txtConfirmation | JTextField | تأكيد كلمة المرور الجديدة |
6 | lstTracking | JList | رسائل تتبع الاتصال |
7 | lstDialogue | JList | رسائل حوار العميل/الخادم |
10 | cmdCancel | JButton | غير معروض - زر يظهر عندما يكون الاتصال بالخادم قيد التقدم. يسمح لك بإيقافه. |
8.5.4.3. القوائم
العنوان | اسم عنصر التحكم | الدور |
اتصال | loginmenu | |
اتصال | mnuconnect | يبدأ الاتصال بالخادم |
خروج | mnuQuitter | يخرج من التطبيق |
الرسائل | mnuMessages | |
مسح السجل | mnuClearTracking | يُفرغ قائمة lstSuivi |
مسح الحوار | mnuClearDialog | مسح قائمة lstDialogue |
المؤلف | mnuAuthor | يعرض مربع حقوق النشر |
8.5.4.4. كيفية عمل التطبيق
عند تحميل الصفحة الرئيسية للتطبيق، تحدث الإجراءات التالية:
- يتم توسيط الصفحة على الشاشة
- تكون خيارات قائمة "تسجيل الدخول/تسجيل الخروج" و"المؤلف" هي الخيارات النشطة فقط
- يتم إخفاء زر "إلغاء"
- تكون قائمتا LstSuivi و LstDialogue فارغتين
هذا الخيار متاح فقط عند ملء الحقول من 1 إلى 5. يؤدي النقر على هذا الخيار إلى تنفيذ الإجراءات التالية:
- يتم تشغيل مؤشر ترابط لإنشاء الاتصال بالخادم
- يظهر زر "إلغاء" للسماح للمستخدم بقطع الاتصال الجاري
- يتم تعطيل جميع خيارات القائمة باستثناء "خروج" و"المؤلف"
ثم تكون تسلسل الأحداث كما يلي:
- نقر المستخدم على زر "إلغاء": يتم إيقاف مؤشر ترابط الاتصال، وتعود القائمة إلى حالتها الأولية. يشير السجل إلى أن المستخدم أغلق الاتصال.
- يتم قبول طلب الاتصال من قبل الخادم. ثم نبدأ الحوار مع الخادم لتغيير كلمة المرور. يتم تسجيل التبادلات في هذا الحوار في قائمة LstDialogue. بمجرد اكتمال الحوار، يتم إغلاق الاتصال بالخادم وتعود قائمة النموذج إلى حالتها الأولية.
- طالما أن الحوار نشط، يظل زر "إلغاء" مرئيًا للسماح للمستخدم بإغلاق الاتصال إذا رغب في ذلك.
- في حالة حدوث أي خطأ أثناء الاتصال، يتم إغلاق الاتصال وعرض سبب الخطأ في قائمة التتبع LstSuivi.
يؤدي هذا الخيار إلى إغلاق أي اتصال نشط بالخادم والخروج من التطبيق.
مسح قوائم LstSuivi و LstDialogue، على التوالي. يتم تعطيل هذين الخيارين عندما تكون القوائم المقابلة فارغة.
يظهر هذا الزر، الموجود في أسفل النموذج، فقط عندما يكون العميل متصلاً بالخادم أو يحاول الاتصال به. يتيح زر "إلغاء" للمستخدم إنهاء الاتصال بالخادم.
تتتبع قائمة LstSuivi (5) الاتصال. وهي تشير إلى اللحظات الرئيسية في الاتصال:
-
إنشاؤه من قبل العميل
-
إغلاقه من قبل الخادم أو العميل
-
أي أخطاء قد تحدث أثناء نشاط الاتصال
تتتبع قائمة LstDialogue (6) الحوار بين العميل والخادم.
تفتح هذه القائمة نافذة تسمى نافذة حقوق النشر:

يتم الإبلاغ عن أخطاء الاتصال في قائمة التتبع 6، بينما يتم الإبلاغ عن الأخطاء المتعلقة بالحوار بين العميل والخادم في قائمة الحوار 7. في حالة حدوث خطأ في الاتصال، يتم إغلاق الحوار بين العميل والخادم وإعادة تعيين النموذج إلى حالته الأولية، ليكون جاهزًا لاتصال جديد.
8.5.4.5. المهمة
قم بتنفيذ العمل الموصوف أعلاه كتطبيق مستقل ثم كبرنامج صغير.