Skip to content

9. برمجة TCP/IP

9.1. معلومات عامة

9.1.1. بروتوكولات الإنترنت

نقدم هنا مقدمة لبروتوكولات الاتصال عبر الإنترنت، والمعروفة أيضًا باسم مجموعة بروتوكولات TCP/IP (بروتوكول التحكم في الإرسال / بروتوكول الإنترنت)، والتي سُميت على اسم البروتوكولين الرئيسيين. يُنصح القارئ بأن يكون لديه فهم عام لكيفية عمل الشبكات، وبشكل خاص بروتوكولات TCP/IP، قبل الشروع في تطوير التطبيقات الموزعة.

النص التالي هو ترجمة جزئية لنص موجود في الوثيقة "LAN Workplace for DOS - Administrator's Guide" من NOVELL، وهي وثيقة تعود إلى أوائل التسعينيات.

-----------------------------------

ينبع المفهوم العام لإنشاء شبكة من أجهزة الكمبيوتر غير المتجانسة من الأبحاث التي أجرتها وكالة DARPA (وكالة مشاريع الأبحاث المتقدمة للدفاع) في الولايات المتحدة. طورت DARPA مجموعة البروتوكولات المعروفة باسم TCP/IP، والتي تسمح للأجهزة غير المتجانسة بالتواصل مع بعضها البعض. تم اختبار هذه البروتوكولات على شبكة تسمى ARPAnet، والتي أصبحت فيما بعد الإنترنت. تحدد بروتوكولات TCP/IP تنسيقات وقواعد الإرسال والاستقبال التي تكون مستقلة عن بنية الشبكة والأجهزة المستخدمة.

الشبكة التي صممتها DARPA وتديرها بروتوكولات TCP/IP هي شبكة مبدلة بالحزم. تنقل هذه الشبكة المعلومات عبر الشبكة في أجزاء صغيرة تسمى الحزم. وبالتالي، إذا أرسل جهاز كمبيوتر ملفًا كبيرًا، فسيتم تقسيمه إلى أجزاء صغيرة يتم إرسالها عبر الشبكة ليتم إعادة تجميعها في الوجهة. يحدد TCP/IP تنسيق هذه الحزم، وهي:

  • مصدر الحزمة
  • الوجهة
  • الطول
  • النوع

9.1.2. نموذج OSI

تتبع بروتوكولات TCP/IP عمومًا نموذج الشبكة المفتوحة المعروف باسم OSI (نموذج مرجعي لترابط الأنظمة المفتوحة) الذي حددته منظمة ISO (منظمة المعايير الدولية). يصف هذا النموذج شبكة مثالية حيث يمكن تمثيل الاتصال بين الأجهزة بنموذج مكون من سبع طبقات:

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

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

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

وتتمثل وظائف الطبقات المختلفة فيما يلي:

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

9.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 (بروتوكول مخطط بيانات المستخدم)
يضمن توصيل المعلومات بشكل غير موثوق بين عميلين

طبقات التطبيق/العرض/الجلسة

توجد بروتوكولات متنوعة هنا:

TELNET
محاكي محطة طرفية يسمح للجهاز A بالاتصال بالجهاز B كمحطة طرفية
FTP (بروتوكول نقل الملفات)
يتيح نقل الملفات
TFTP (بروتوكول نقل الملفات البسيط
)
يتيح نقل الملفات
SMTP (بروتوكول نقل البريد البسيط
)
يتيح تبادل الرسائل بين مستخدمي الشبكة
نظام أسماء النطاقات (DNS)
يحول اسم الجهاز إلى عنوان الإنترنت الخاص به
XDR (تمثيل البيانات الخارجية
)
تم إنشاؤه بواسطة Sun Microsystems، ويحدد معيارًا لتمثيل البيانات المستقل عن الجهاز
RPC (استدعاء الإجراءات عن بُعد)
تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يحرر المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR
NFS (نظام ملفات الشبكة)
تم تعريفه أيضًا بواسطة Sun، ويسمح هذا البروتوكول لجهاز واحد بـ"رؤية" نظام الملفات الخاص بجهاز آخر. وهو يعتمد على بروتوكول RPC المذكور أعلاه

9.1.4. كيفية عمل بروتوكولات الإنترنت

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

لنأخذ مثالاً: تطبيق FTP، المحدد في طبقة التطبيقات، والذي يتيح نقل الملفات بين الأجهزة.

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

9.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 فريدة. في فرنسا، تتولى INRIA مسؤولية تخصيص عناوين IP. في الواقع، تخصص هذه المنظمة عنوانًا لشبكتك المحلية، على سبيل المثال 193.49.144.0 لشبكة كلية العلوم في أنجيه. يمكن لمسؤول هذه الشبكة بعد ذلك تخصيص عناوين IP من 193.49.144.1 إلى 193.49.144.254 حسبما يراه مناسبًا. يتم تخزين هذا العنوان عمومًا في ملف محدد على كل جهاز متصل بالشبكة.

9.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 بتات) ويمتد عنوان المضيف على 1 بايت. وبالتالي، يمكن أن يكون هناك 2²¹ شبكة من الفئة C، تحتوي كل منها على ما يصل إلى 256 مضيفًا.

وبما أن عنوان جهاز Lagaffe في كلية العلوم بمدينة أنجيه هو 193.49.144.1، نلاحظ أن البايت الأعلى قيمة هو 193، وهو 11000001 بالثنائي. يمكننا استنتاج أن الشبكة هي شبكة من الفئة C.

العناوين المحجوزة

  • بعض عناوين IP هي عناوين شبكة وليست عناوين عقد داخل الشبكة. وهي تلك التي يكون فيها عنوان العقدة محددًا بـ 0. وبالتالي، فإن العنوان 193.49.144.0 هو عنوان IP لشبكة كلية العلوم في أنجيه. وبالتالي، لا يمكن لأي عقدة في الشبكة أن يكون لها العنوان صفر.
  • عندما يتكون عنوان العقدة في عنوان IP بالكامل من أرقام 1، فإنه يكون عنوان بث: يشير هذا العنوان إلى جميع العقد الموجودة على الشبكة.
  • في شبكة من الفئة C، التي تسمح نظريًا بـ 2⁸ = 256 عقدة، إذا أزلنا العنوانين المحظورين، يتبقى لدينا 254 عنوانًا مسموحًا به فقط.

9.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. يجب أن يكون لخادم RARP عادةً عنوان IP ثابت يجب أن يكون قادرًا على معرفته دون الحاجة إلى استخدام بروتوكول RARP نفسه.

9.1.6. طبقة الشبكة، المعروفة باسم طبقة بروتوكول الإنترنت (IP)

يحدد بروتوكول الإنترنت (IP) التنسيق الذي يجب أن تتخذه الحزم وكيفية التعامل معها أثناء الإرسال أو الاستقبال. يُطلق على هذا النوع المحدد من الحزم اسم مخطط بيانات IP. وقد ناقشنا ذلك سابقًا:

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

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

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

9.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، وعنوانه داخل الشبكة رقم 2 هو 193.49.145.3.

يتمثل دور جهاز التوجيه في أخذ حزمة IP التي يتلقاها — والموجودة داخل إطار مادي نموذجي للشبكة رقم 1 — ووضعها في إطار مادي يمكنه الانتقال عبر الشبكة رقم 2. إذا كان عنوان IP لوجهة الحزمة موجودًا داخل الشبكة رقم 2، فسيرسل جهاز التوجيه الحزمة إليها مباشرةً؛ وإلا، فسيرسلها إلى جهاز توجيه آخر، يربط الشبكة رقم 2 بالشبكة رقم 3، وهكذا دواليك.

9.1.6.2. رسائل الخطأ والتحكم

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

9.1.7. طبقة النقل: بروتوكولا UDP و TCP

9.1.7.1. بروتوكول UDP: بروتوكول مخطط بيانات المستخدم

يسمح بروتوكول UDP بتبادل غير موثوق للبيانات بين نقطتين، مما يعني أن التسليم الناجح لحزمة إلى وجهتها غير مضمون. يمكن للتطبيق، إذا اختار ذلك، التعامل مع هذا الأمر بنفسه، على سبيل المثال عن طريق انتظار إقرار بالاستلام بعد إرسال رسالة قبل إرسال الرسالة التالية.

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

سيتم تغليف هذه الداتاغرامات في حزم IP، ثم في إطارات مادية.

9.1.7.2. بروتوكول TCP: بروتوكول التحكم في الإرسال

بالنسبة للاتصالات الآمنة، فإن بروتوكول UDP غير كافٍ: يجب على مطور التطبيق إنشاء بروتوكول بنفسه لضمان توجيه الحزم بشكل صحيح.

يتجنب بروتوكول TCP (بروتوكول التحكم في الإرسال) هذه المشاكل. وفيما يلي خصائصه:

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

9.1.8. طبقة التطبيق

فوق بروتوكولي UDP و TCP، توجد بروتوكولات قياسية متنوعة:

TELNET

يسمح هذا البروتوكول لمستخدم على الجهاز A في الشبكة بالاتصال بالجهاز B (الذي يُسمى غالبًا الجهاز المضيف). يحاكي TELNET ما يُسمى بالمحطة الطرفية العالمية على الجهاز A. وبالتالي، يتصرف المستخدم كما لو كان لديه محطة طرفية متصلة بالجهاز B. يعتمد Telnet على بروتوكول TCP.

FTP: (بروتوكول نقل الملفات)

يتيح هذا البروتوكول تبادل الملفات بين جهازين بعيدين، بالإضافة إلى إجراء عمليات على الملفات مثل إنشاء الدلائل، على سبيل المثال. ويعتمد على بروتوكول TCP.

TFTP: (بروتوكول نقل الملفات البسيط)

هذا البروتوكول هو أحد أشكال بروتوكول FTP. يعتمد على بروتوكول UDP وهو أقل تعقيدًا من بروتوكول FTP.

DNS: (نظام أسماء النطاقات)

عندما يرغب المستخدم في تبادل الملفات مع جهاز بعيد، عبر FTP على سبيل المثال، يجب أن يعرف عنوان الإنترنت الخاص بذلك الجهاز. على سبيل المثال، لاستخدام FTP على جهاز Lagaffe في جامعة أنجيه، ستحتاج إلى تشغيل FTP على النحو التالي: FTP 193.49.144.1

وهذا يتطلب دليلًا يربط الأجهزة بعناوين IP. في هذا الدليل، من المرجح أن يتم تعيين الأجهزة بأسماء رمزية مثل:

جهاز DPX2/320 في جامعة أنجيه

جهاز Sun في ISERPA في أنجيه

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

تم تسمية جهاز DPX2/320 في جامعة أنجيه باسم Lagaffe، بينما تم تسمية جهاز كمبيوتر شخصي 486DX50 باسم liny. كيف يمكنك الإشارة إلى هذه الأجهزة من الخارج؟ عن طريق تحديد التسلسل الهرمي للنطاقات التي تنتمي إليها. وبالتالي، سيكون الاسم الكامل لجهاز Lagaffe هو:

Lagaffe.univ-Angers.fr

يمكن استخدام الأسماء النسبية داخل النطاقات. وبالتالي، داخل نطاق fr وخارج نطاق univ-Angers، يمكن الإشارة إلى جهاز Lagaffe على النحو التالي

Lagaffe.univ-Angers

وأخيرًا، داخل نطاق univ-Angers، يمكن الإشارة إليها ببساطة على النحو التالي

Lagaffe

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

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

XDR: (تمثيل البيانات الخارجية)

تم إنشاء هذا البروتوكول بواسطة Sun Microsystems، وهو يحدد تمثيلًا قياسيًا للبيانات مستقلًا عن الجهاز.

RPC: (استدعاء الإجراءات عن بُعد)

تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يعفي المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR

NFS: نظام ملفات الشبكة

تم تعريفه أيضًا بواسطة Sun، ويسمح هذا البروتوكول لجهاز واحد بـ"رؤية" نظام الملفات الخاص بجهاز آخر. وهو يعتمد على بروتوكول RPC المذكور أعلاه.

9.1.9. الخلاصة

في هذه المقدمة، عرضنا بعض السمات الرئيسية لبروتوكولات الإنترنت. وللتعمق أكثر في هذا المجال، يمكن للقراء الرجوع إلى كتاب دوغلاس كومر الممتاز:

العنوان
TCP/IP: الهندسة المعمارية والبروتوكولات والتطبيقات.
المؤلف
دوغلاس كومر
الناشر
إنتر إديشنز

9.2. إدارة عناوين الشبكة

يتم تعريف جهاز على الإنترنت بشكل فريد بواسطة عنوان IP (بروتوكول الإنترنت) بالصيغة I1.I2.I3.I4، حيث I1 هو رقم بين 1 و 254. ويمكن أيضًا تعريفه بواسطة اسم فريد. هذا الاسم ليس ضروريًا، حيث تستخدم التطبيقات في النهاية دائمًا عناوين IP للأجهزة. وتوجد هذه الأسماء لتسهيل الأمور على المستخدمين. وبالتالي، من الأسهل استخدام متصفح لطلب عنوان URL http://www.ibm.com بدلاً من عنوان URL http://129.42.17.99، على الرغم من أن كلا الطريقتين ممكنتان. تتم معالجة التعيين بين عنوان IP <--> اسم الجهاز بواسطة خدمة إنترنت موزعة تسمى DNS (نظام أسماء النطاقات). توفر منصة .NET فئة Dns لإدارة عناوين الإنترنت:

Image

معظم أساليب الفئة ثابتة. دعونا نلقي نظرة على تلك التي تهمنا:

تحميلات إضافية Public Shared Function
 GetHostByAddress(ByVal address As
 String) As IPHostEntry
تُرجع IPHostEntry من عنوان IP بالصيغة "I1.I2.I3.I4". تُطلق استثناءً إذا تعذر العثور على عنوان الجهاز.
دالة عامة مشتركة
 GetHostByName(ByVal hostName As
 String) As IPHostEntry
تُرجع IPHostEntry من اسم جهاز. تُطلق استثناءً إذا تعذر العثور على اسم الجهاز.
دالة عامة مشتركة
GetHostName() كسلسلة
تُرجع اسم الجهاز الذي يعمل عليه البرنامج الذي ينفذ هذه التعليمات

تتخذ عناوين شبكة IPHostEntry الشكل التالي:

الخصائص التي تهمنا:

الخاصية العامة AddressList
كـ IPAddress ()
قائمة بعناوين IP الخاصة بجهاز ما. في حين أن عنوان IP يحدد جهازًا ماديًا واحدًا فقط، قد يكون للجهاز المادي عدة عناوين IP. ويحدث هذا إذا كان الجهاز مزودًا بعدة بطاقات شبكة تربطه بشبكات مختلفة.
الاسم المستعار للخاصية العامة
كـ String ()
قائمة بأسماء مستعارة لجهاز، والتي يمكن تحديدها بواسطة اسم أساسي وأسماء مستعارة
خاصية عامة HostName
كـ String
اسم الجهاز، إن كان له اسم

من فئة IPAddress، سنستخدم المنشئ والخصائص والطرق التالية:

Image

يمكن تحويل كائن [IPAddress] إلى السلسلة I1.I2.I3.I4 باستخدام الطريقة ToString(). وبالمقابل، يمكن الحصول على كائن IPAddress من السلسلة I1.I2.I3.I4 باستخدام الطريقة الثابتة IPAddress.Parse("I1.I2.I3.I4"). انظر إلى البرنامج التالي، الذي يعرض اسم الجهاز الذي يعمل عليه ثم يوفر بشكل تفاعلي تعيينات عنوان IP <--> اسم الجهاز:

dos>address1
Machine Locale=tahe

Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171

Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa

Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99

Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99

Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]

Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : fin

البرنامج كالتالي:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
 
' test module
Public Module adresses
 
    Sub Main()
        ' displays the name of the local machine
        ' then interactively provides information on network machines
        ' identified by name or address IP
        ' local machine
        Dim localHost As String = Dns.GetHostName()
        Console.Out.WriteLine(("Machine Locale=" + localHost))
 
        ' interactive Q&A
        Dim machine As String
        Dim adresseMachine As IPHostEntry
        While True
            ' enter the name of the machine you are looking for
            Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
            machine = Console.In.ReadLine().Trim().ToLower()
            ' finished?
            If machine = "fin" Then
                Exit While
            End If
 
            ' address I1.I2.I3.I4 or machine name?
            Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
            ' management exception
            Try
                If isIPV4 Then
                    adresseMachine = Dns.GetHostByAddress(machine)
                Else
                    adresseMachine = Dns.GetHostByName(machine)
                End If
                ' the name
                Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
                ' IP addresses
                Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
                Dim i As Integer
                For i = 1 To adresseMachine.AddressList.Length - 1
                    Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
                Next i
                Console.Out.WriteLine()
                ' aliases
                If adresseMachine.Aliases.Length <> 0 Then
                    Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
                    For i = 1 To adresseMachine.Aliases.Length - 1
                        Console.Out.Write(("," + adresseMachine.Aliases(i)))
                    Next i
                    Console.Out.WriteLine()
                End If
            Catch
                ' the machine doesn't exist
                Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
            End Try
        End While
    End Sub
End Module

9.3. برمجة TCP/IP

9.3.1. معلومات عامة

لنفترض وجود اتصال بين جهازين بعيدين A و B:

عندما يرغب تطبيق 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: هاتان الخدمتان لا تقبلان نفس الأوامر. فلهما بروتوكولات اتصال مختلفة.

9.3.2. خصائص بروتوكول TCP

هنا، سنقوم فقط بفحص اتصالات الشبكة باستخدام بروتوكول النقل TCP. دعونا نستعرض خصائصه:

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

9.3.3. علاقة العميل-الخادم

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

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

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

9.3.6. فئة TcpClient

فئة TcpClient هي الفئة المناسبة لتمثيل عميل خدمة TCP. وهي معرّفة على النحو التالي:

Image

فيما يلي المنشئات والأساليب والخصائص التي تهمنا:

Public Sub New(ByVal hostname
 As String, ByVal port As Integer)
ينشئ اتصال TCP مع الخادم الذي يعمل على المنفذ المحدد (port) للجهاز المحدد (hostname). على سبيل المثال، new TcpClient("istia.univ-angers.fr", 80) للاتصال بالمنفذ 80 للجهاز istia.univ-angers.fr
Public Sub Close()
يغلق الاتصال بخادم TCP
Public Function GetStream()
 كـ NetworkStream
يحصل على NetworkStream للقراءة من الخادم والكتابة إليه. يتيح هذا الدفق الاتصال بين العميل والخادم.

9.3.7. فئة NetworkStream

تمثل فئة NetworkStream دفق الشبكة بين العميل والخادم. يتم تعريف الفئة على النحو التالي:

Image

تُشتق فئة NetworkStream من فئة Stream. تتبادل العديد من تطبيقات العميل-الخادم أسطر نصية تنتهي بأحرف نهاية السطر "\r\n". ولذلك، من المفيد استخدام كائنات StreamReader و StreamWriter لقراءة وكتابة هذه الأسطر في دفق الشبكة. وعندما يتواصل جهازي كمبيوتر، يوجد كائن TcpClient في كل طرف من أطراف الاتصال. توفر طريقة GetStream لهذا الكائن الوصول إلى دفق الشبكة (NetworkStream) الذي يربط بين الجهازين. وبالتالي، إذا كان الجهاز M1 قد أنشأ اتصالاً مع الجهاز M2 باستخدام كائن TcpClient client1 وكانا يتبادلان أسطر النص، فيمكنه إنشاء دفق القراءة والكتابة الخاص به على النحو التالي:

Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true

العبارة

out1.AutoFlush=true

أن دفق الكتابة من client1 لن يمر عبر مخزن مؤقت وسيذهب مباشرة إلى الشبكة. هذه النقطة مهمة. بشكل عام، عندما يرسل client1 سطر نص إلى شريكه، فإنه يتوقع رداً. لن يصل هذا الرد أبداً إذا تم تخزين السطر فعلياً في الجهاز M1 ولم يتم إرساله أبداً. لإرسال سطر نص إلى الجهاز M2، نكتب:

client1.WriteLine("un texte")

لقراءة الرد من M2، نكتب:

Dim réponse as String=client1.ReadLine()

9.3.8. البنية الأساسية لعميل الإنترنت

لدينا الآن العناصر اللازمة لكتابة البنية الأساسية لعميل الإنترنت:


    Dim client As TcpClient = Nothing     ' the customer
    Dim [IN] As StreamReader = Nothing ' the customer's reading flow
    Dim OUT As StreamWriter = Nothing     ' the customer's writing flow
    Dim demande As String = Nothing         ' customer request
    Dim réponse As String = Nothing         ' server response
 
    Try
      ' connect to the service running on port P of machine M
      client = New TcpClient(nomServeur, port)
 
      ' create customer input/output flows TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True
 
      ' request-response loop
      While True
        ' preparing the application
        demande = ...
        ' we send it to the server
        OUT.WriteLine(demande)
        ' we read the server response
        réponse = [IN].ReadLine()
        ' the answer is processed
        ...
            End While
            ' it's over
            client.Close()
        Catch ex As Exception
      ' we handle the exception
...
        End Try

9.3.9. فئة TcpListener

فئة TcpListener هي الفئة المناسبة لتمثيل خدمة TCP. وهي معرّفة على النحو التالي:

Image

فيما يلي المنشئات والطرق والخصائص التي تهمنا:

Public Sub New(ByVal localaddr
As IPAddress, ByVal port As Integer)
تنشئ خدمة TCP تستمع لطلبات العملاء على منفذ يتم تمريره كمعلمة (port)، والمعروف باسم منفذ الاستماع للجهاز المحلي الذي يحمل عنوان IP localaddr.
Public Function AcceptTcpClient()
As TcpClient
يقبل طلب العميل. يُرجع كائن TcpClient مرتبط بمنفذ آخر، يُسمى منفذ الخدمة.
Public Sub Start()
يبدأ في الاستماع لطلبات العميل
Public Sub Stop()
توقف الاستماع لطلبات العميل

9.3.10. البنية الأساسية لخادم الإنترنت

من ما رأيناه سابقًا، يمكننا استنتاج البنية الأساسية للخادم:


    ' we create the listening service
    Dim ecoute As TcpListener = Nothing
    Dim port As Integer = ...
    Try
      ' create the service
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' launch it
      ecoute.Start()
      ' service loop
      Dim liaisonClient As TcpClient = Nothing
            While not fini
                ' waiting for a customer
                liaisonClient = ecoute.AcceptTcpClient()
                ' the service is provided by another task
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
                tache.Start()
            End While
        Catch ex As Exception
            ' we report the error
....
        End Try
        ' end of service
        ecoute.Stop()

فئة Service هي مؤشر ترابط قد يبدو كالتالي:


Public Class Service
 
    Private liaisonClient As TcpClient    ' customer liaison
    Private [IN] As StreamReader    ' iNPUTS
    Private OUT As StreamWriter    ' output flow
 
    ' manufacturer
    Public Sub New(ByVal liaisonClient As TcpClient, ...)
        Me.liaisonClient = liaisonClient
        ...
    End Sub
 
    ' run method
    Public Sub Run()
        ' renders service to the customer
        Try
            ' iNPUTS
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' output flow
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' loop read request/write response
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' we process demand
                ...
                ' we send the answer
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' following request
                demande = [IN].ReadLine
            End While
            ' end link
            liaisonClient.Close()
        Catch e As Exception
            ...
        End Try
        ' end of service
    End Sub

9.4. أمثلة

9.4.1. خادم Echo

سنكتب خادم إيكو سيتم تشغيله من نافذة DOS باستخدام الأمر:

serveurEcho port

يعمل الخادم على المنفذ الذي تم تمريره كمعلمة. وهو ببساطة يعيد إلى العميل الطلب الذي أرسله العميل إليه. البرنامج كالتالي:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
 
' call: serveurEcho port
' echo server
' returns the line sent to the customer
 
Public Class serveurEcho
  Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
 
  ' main program
  Public Shared Sub Main(ByVal args() As String)
 
    ' is there an argument
    If args.Length <> 1 Then
      erreur(syntaxe, 1)
    End If
    ' this argument must be integer >0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(0))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    ' we create the listening service
    Dim ecoute As TcpListener = Nothing
    Dim nbClients As Integer = 0 ' of customers handled
    Try
      ' create the service
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' launch it
      ecoute.Start()
      ' follow-up
      Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
      Console.Out.WriteLine(ecoute.LocalEndpoint)
 
      ' service loop
      Dim liaisonClient As TcpClient = Nothing
            While True
                ' infinite loop - will be stopped by Ctrl-C
                ' waiting for a customer
                liaisonClient = ecoute.AcceptTcpClient()
 
                ' the service is provided by another task
                nbClients += 1
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
                tache.Start()
            End While
            ' back to listening to requests
        Catch ex As Exception
            ' we report the error
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
        ' end of service
        ecoute.Stop()
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class
 
' -------------------------------------------------------
' provides service to an echo server client
Public Class traiteClientEcho
 
    Private liaisonClient As TcpClient    ' customer liaison
    Private numClient As Integer    ' customer no
    Private [IN] As StreamReader    ' iNPUTS
    Private OUT As StreamWriter    ' output flow
 
    ' manufacturer
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
        Me.liaisonClient = liaisonClient
        Me.numClient = numClient
    End Sub

    ' run method
    Public Sub Run()
        ' renders service to the customer
        Console.Out.WriteLine(("Début de service au client " & numClient))
        Try
            ' iNPUTS
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' output flow
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' loop read request/write response
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' follow-up
                Console.Out.WriteLine(("Client " & numClient & " : " & demande))
                ' the service stops when the client sends an end-of-file marker
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' service stops when client sends "end
                If demande.Trim().ToLower() = "fin" Then
                    Exit While
                End If
                ' following request
                demande = [IN].ReadLine
            End While
            ' end link
            liaisonClient.Close()
        Catch e As Exception
            erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
        End Try
        ' end of service
        Console.Out.WriteLine(("Fin de service au client " & numClient))
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class

تتوافق بنية الخادم مع البنية العامة لخوادم TCP.

9.4.2. عميل لخادم الصدى

سنقوم الآن بكتابة عميل للخادم السابق. وسيتم استدعاؤه على النحو التالي:

clientEcho nomServeur port

يتصل هذا البرنامج بالجهاز servername على المنفذ port ثم يرسل أسطر من النص إلى الخادم، الذي يعيد إرسالها مرة أخرى.


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
 
Public Class clientEcho
 
  ' connects to an echo server
  ' any line typed on the keyboard is then received as an echo
  Public Shared Sub Main(ByVal args() As String)
    ' syntax
    Const syntaxe As String = "pg machine port"
 
    ' number of arguments
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' note the server name
    Dim nomServeur As String = args(0)
 
    ' port must be integer >0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
 
        ' we can work
    Dim client As TcpClient = Nothing ' the customer
    Dim [IN] As StreamReader = Nothing ' the customer's reading flow
    Dim OUT As StreamWriter = Nothing ' the customer's writing flow
    Dim demande As String = Nothing ' customer request
    Dim réponse As String = Nothing ' server response
    Try
      ' connect to the service running on port P of machine M
      client = New TcpClient(nomServeur, port)
 
      ' create customer input/output flows TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True
 
      ' request-response loop
      While True
        ' demand comes from the keyboard
        Console.Out.Write("demande (fin pour arrêter) : ")
        demande = Console.In.ReadLine()
        ' we send it to the server
        OUT.WriteLine(demande)
        ' we read the server response
        réponse = [IN].ReadLine()
        ' the answer is processed
        Console.Out.WriteLine(("Réponse : " + réponse))
        ' finished?
        If demande.Trim().ToLower() = "fin" Then
          Exit While
                End If
            End While
            ' it's over
            client.Close()
        Catch ex As Exception
      ' we handle the exception
      erreur(ex.Message, 3)
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class

يتوافق هيكل هذا العميل مع البنية العامة لعملاء TCP. وفيما يلي النتائج التي تم الحصول عليها في التكوين التالي:

  • يعمل الخادم على المنفذ 100 في نافذة DOS
  • على نفس الجهاز، يتم تشغيل عميلين في نافذتي DOS أخريين

في نافذة العميل 1، لدينا النتائج التالية:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]

في العميل 2:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]

على جانب الخادم:

dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C

لاحظ أن الخادم تمكن بالفعل من خدمة عميلين في وقت واحد.

9.4.3. عميل TCP عام

تعمل العديد من الخدمات التي تم إنشاؤها في بدايات الإنترنت وفقًا لنموذج خادم الصدى الذي تمت مناقشته سابقًا: تتكون التبادلات بين العميل والخادم من تبادل أسطر نصية. سنكتب عميل TCP عامًا سيتم تشغيله على النحو التالي: cltgen server port

سيتصل عميل TCP هذا بالمنفذ port على الخادم server. بمجرد الاتصال، سيقوم بإنشاء مؤشرين:

  1. خيط مسؤول عن قراءة الأوامر المكتوبة على لوحة المفاتيح وإرسالها إلى الخادم
  2. خيط مسؤول عن قراءة استجابات الخادم وعرضها على الشاشة

لماذا خيطان بينما لم يكن ذلك ضروريًا في التطبيق السابق؟ في ذلك التطبيق، كان بروتوكول الاتصال ثابتًا: أرسل العميل سطرًا واحدًا، ورد الخادم بسطر واحد. لكل خدمة بروتوكولها الخاص، ونواجه أيضًا الحالات التالية:

  • يجب على العميل إرسال عدة أسطر من النص قبل تلقي الرد
  • قد يحتوي رد الخادم على عدة أسطر من النص

لذلك، فإن الحلقة التي ترسل سطرًا واحدًا إلى الخادم وتستقبل سطرًا واحدًا من الخادم ليست مناسبة دائمًا. لذلك سننشئ حلقتين منفصلتين:

  • حلقة لقراءة الأوامر المكتوبة على لوحة المفاتيح لإرسالها إلى الخادم. سيشير المستخدم إلى نهاية الأوامر باستخدام الكلمة الرئيسية "fin".
  • حلقة لاستقبال وعرض ردود الخادم. ستكون هذه حلقة لا نهائية لن تتوقف إلا بإغلاق الخادم للاتصال بالشبكة أو بكتابة المستخدم للأمر "end" على لوحة المفاتيح.

للحصول على هاتين الحلقتين المنفصلتين، نحتاج إلى مؤشرين مستقلين. لنلقِ نظرة على مثال للتنفيذ حيث يتصل عميل TCP العام لدينا بخدمة SMTP (بروتوكول نقل البريد البسيط). هذه الخدمة مسؤولة عن توجيه البريد الإلكتروني إلى المستلمين. وهي تعمل على المنفذ 25 وتستخدم بروتوكول تبادل نصي.

dos>cltgen 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 رسالة ترحيب عندما يتصل بها العميل:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • تحتوي بعض الخدمات على أمر "help" يوفر معلومات عن الأوامر المتاحة لتلك الخدمة. هذا ليس هو الحال هنا. أوامر SMTP المستخدمة في المثال هي كما يلي:
    • mail from: sender، لتحديد عنوان البريد الإلكتروني للمرسل
    • rcpt to: recipient، لتحديد عنوان البريد الإلكتروني لمستلم الرسالة. إذا كان هناك عدة مستلمين، يتم تكرار الأمر rcpt to: عدة مرات حسب الحاجة لكل مستلم.
    • data الذي يشير إلى خادم SMTP بأن رسالة على وشك الإرسال. كما هو موضح في استجابة الخادم، تتكون هذه البيانات من سلسلة من الأسطر تنتهي بسطر يحتوي على نقطة فقط. قد تحتوي الرسالة على رؤوس مفصولة عن نص الرسالة بسطر فارغ. في مثالنا، قمنا بتضمين موضوع باستخدام الكلمة الرئيسية Subject:
  • بمجرد إرسال الرسالة، يمكنك إخبار الخادم بأنك انتهيت باستخدام الأمر quit. ثم يقوم الخادم بإغلاق اتصال الشبكة. يمكن لسلسلة القراءة اكتشاف هذا الحدث والتوقف.
  • ثم يكتب المستخدم "end" على لوحة المفاتيح لإيقاف مؤشر الترابط الذي يقرأ الأوامر المكتوبة على لوحة المفاتيح أيضًا.

إذا تحققنا من البريد الإلكتروني المستلم، فسنرى ما يلي (Outlook):

Image

لاحظ أن خدمة SMTP لا يمكنها اكتشاف ما إذا كان المرسل صالحًا أم لا. لذلك، لا يمكنك أبدًا الوثوق بحقل "من" في الرسالة. هنا، لم يكن المرسل machin@univ-angers.fr موجودًا. يتيح لنا عميل TCP العام هذا اكتشاف بروتوكول اتصال خدمات الإنترنت، ومن ثم إنشاء فئات متخصصة لعملاء هذه الخدمات. دعونا نستكشف بروتوكول اتصال خدمة POP (بروتوكول مكتب البريد)، الذي يسمح للمستخدمين باسترداد رسائل البريد الإلكتروني المخزنة على الخادم. يعمل هذا البروتوكول على المنفذ 110.

dos>cltgen 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]

الأوامر الرئيسية هي كما يلي:

  • تسجيل دخول المستخدم، حيث تقوم بإدخال معلومات تسجيل الدخول الخاصة بك على الجهاز الذي يستضيف بريدك الإلكتروني
  • pass password، حيث تدخل كلمة المرور المرتبطة بتسجيل الدخول السابق
  • list، للحصول على قائمة بالرسائل بالصيغة رقم، الحجم بالبايت
  • retr i، لقراءة الرسالة رقم i
  • الخروج، لإنهاء الجلسة.

الآن دعونا نستكشف بروتوكول الاتصال بين العميل وخادم الويب، الذي يعمل عادةً على المنفذ 80:

dos>cltgen 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]

يرسل عميل الويب أوامره إلى الخادم وفقًا للنمط التالي:

commande1
commande2
...
commanden
[ligne vide]

لا يستجيب خادم الويب إلا بعد استلام السطر الفارغ. في هذا المثال، استخدمنا أمرًا واحدًا فقط:

GET /index.html HTTP/1.0

الذي يطلب عنوان 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

يشير إلى أن خادم الويب الذي تم الاتصال به يدعم بروتوكول HTTP/1.1 وأنه عثر بنجاح على الملف المطلوب (200 OK)، حيث 200 هو رمز استجابة HTTP. الأسطر

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

يخبر العميل أنه سيتلقى 11,251 بايت من نص HTML (لغة ترميز النص التشعبي) وأن الاتصال سيتم إغلاقه بمجرد إرسال البيانات. إذن لدينا هنا عميل TCP مفيد للغاية. في الواقع، هذا العميل موجود بالفعل على الأجهزة حيث يُسمى telnet، ولكن كان من المثير للاهتمام أن نكتبه بأنفسنا. برنامج عميل TCP العام هو كما يلي:


' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
 
' the class
Public Class clientTcpGénérique
   
    
   ' 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
  Public Shared Sub Main(ByVal args() As String)
 
    ' syntax
    Const syntaxe As String = "pg serveur port"

    ' number of arguments
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' note the server name
    Dim serveur As String = args(0)
 
    ' port must be integer >0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    Dim client As TcpClient = Nothing
    ' there may be problems
    Try
      ' connect to the service
      client = New TcpClient(serveur, port)
    Catch ex As Exception
      ' error
      Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
      ' end
      Return
        End Try
        ' create read/write threads
        Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
        Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
 
        ' start execution of both threads
        thSend.Start()
        thReceive.Start()
 
        ' end of main thread
        Return
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class
 
Public Class clientSend
    ' class for reading keyboard commands
    ' and send them to a server via a tcp client passed to the
    Private client As TcpClient    ' tcp client
 
    ' manufacturer
    Public Sub New(ByVal client As TcpClient)
        ' we note the tcp client
        Me.client = client
    End Sub
 
    ' thread Run method
    Public Sub Run()
 
        ' local data
        Dim OUT As StreamWriter = Nothing        ' network write streams
        Dim commande As String = Nothing        ' command read from keyboard
        ' error management
        Try
            ' create network write stream
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' order entry-send loop
            Console.Out.WriteLine("Commandes : ")
            While True
                ' read command typed on keyboard
                commande = Console.In.ReadLine().Trim()
                ' finished?
                If commande.ToLower() = "fin" Then
                    Exit While
                End If
                ' send order to server
                OUT.WriteLine(commande)
            End While
        Catch ex As Exception
            ' error
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' end - we close the feeds
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' signals the end of the thread
        Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
    End Sub
End Class

 
Public Class clientReceive
    ' class responsible for reading lines of text intended for a 
    ' tcp client passed to builder
    Private client As TcpClient    ' tcp client
 
    ' manufacturer
    Public Sub New(ByVal client As TcpClient)
        ' we note the tcp client
        Me.client = client
    End Sub
 
    'manufacturer
    ' thread Run method
    Public Sub Run()
 
        ' local data
        Dim [IN] As StreamReader = Nothing        ' network read stream
        Dim réponse As String = Nothing        ' server response
        ' error management
        Try
            ' create network read stream
            [IN] = New StreamReader(client.GetStream())
            ' loop read text lines from IN stream
            While True
                ' network streaming
                réponse = [IN].ReadLine()
                ' closed flow?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' display
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' error
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' end - close flows
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' signals the end of the thread
        Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
    End Sub
End Class

9.4.4. خادم TCP عام

الآن سنلقي نظرة على خادم

  • يعرض الأوامر المرسلة من قبل عملائه على الشاشة
  • ويرسل إليهم النص الذي أدخله المستخدم عبر لوحة المفاتيح. وبالتالي، فإن المستخدم هو الذي يقوم بدور الخادم.

يتم تشغيل البرنامج بواسطة: srvgen listeningPort، حيث listeningPort هو المنفذ الذي يجب على العملاء الاتصال به. سيتم التعامل مع خدمة العميل بواسطة مؤشرين:

  • خيط مخصص حصريًا لقراءة أسطر النص المرسلة من العميل
  • خيط مخصص حصريًا لقراءة الردود التي يكتبها المستخدم. سيشير هذا الخيط، باستخدام الأمر fin، إلى أنه يغلق الاتصال مع العميل.

يقوم الخادم بإنشاء خيطين لكل عميل. إذا كان هناك n عميل، فسيكون هناك 2n خيطًا نشطًا في نفس الوقت. لا يتوقف الخادم نفسه أبدًا ما لم يضغط المستخدم على Ctrl-C على لوحة المفاتيح. دعونا نلقي نظرة على بعض الأمثلة.

يعمل الخادم على المنفذ 100، ونستخدم العميل العام للتواصل معه. تبدو نافذة العميل كما يلي:

dos>cltgen 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>srvgen 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>cltgen 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>srvgen 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>srvgen 88
Serveur générique lancé sur le port 88

الآن، لنفتح متصفحًا ونطلب عنوان URL http://localhost:88/exemple.html. سيتصل المتصفح عندئذٍ بالمنفذ 88 على جهاز localhost ويطلب الصفحة /example.html:

Image

الآن دعونا نلقي نظرة على نافذة الخادم لدينا:

dos>srvgen 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". عندئذٍ يدرك المتصفح أن استجابة الخادم قد اكتملت ويمكنه عرضها:

Image

إذا قمت، في المثال أعلاه، بتحديد "عرض/المصدر" (View/Source) لمعرفة ما تلقّاه المتصفح، فستحصل على:

Image

أي، بالضبط ما تم إرساله من الخادم العام. فيما يلي كود خادم TCP العام:


' namespaces
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
 
Public Class serveurTcpGénérique
 
    ' main program
    Public Shared Sub Main(ByVal args() As String)
 
        ' 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
 
        Const syntaxe As String = "Syntaxe : pg port"
 
        ' is there an argument
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        ' this argument must be integer >0
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' we create the listening service
        Dim ecoute As TcpListener = Nothing
        Dim nbClients As Integer = 0     ' of customers handled
        Try
            ' create the service
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
            ' launch it
            ecoute.Start()
            ' follow-up
            Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
 
            ' customer service loop
            Dim client As TcpClient = Nothing
            While True        ' infinite loop - will be stopped by Ctrl-C
                ' waiting for a customer
                client = ecoute.AcceptTcpClient()
 
                ' the service is provided by separate threads
                nbClients += 1
                ' thread for reading customer requests
                Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
                ' thread for reading responses typed on the keyboard by the user
                Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
 
                ' start execution of both threads
                thSend.Start()
                thReceive.Start()
            End While
            ' back to listening to requests
        Catch ex As Exception
            ' we report the error
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class
 
 
Public Class serveurSend
    ' class responsible for reading typed responses
    ' and send them to a client via a tcp client passed to the
    Private client As TcpClient    ' tcp client
    Private numClient As Integer    ' customer no
 
    ' manufacturer
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' we note the tcp client
        Me.client = client
        ' and its
        Me.numClient = numClient
    End Sub
 
    ' thread Run method
    Public Sub Run()
 
        ' local data
        Dim OUT As StreamWriter = Nothing        ' network write streams
        Dim réponse As String = Nothing        ' answer read from keyboard
        ' follow-up
        Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
        ' error management
        Try
            ' network write stream creation
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' order entry-send loop
            While True
                ' customer identification
                Console.Out.Write((numClient & " : "))
                ' read response typed on keyboard
                réponse = Console.In.ReadLine().Trim()
                ' finished?
                If réponse.ToLower() = "fin" Then
                    Exit While
                End If
                ' send response to server
                OUT.WriteLine(réponse)
            End While
            ' following response
        Catch ex As Exception
            ' error
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' end - close flows
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' signals the end of the thread
        Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
    End Sub
End Class
 
Public Class serveurReceive
    ' class responsible for reading text lines sent to the server 
    ' via a tcp client passed to the builder
    Private client As TcpClient     ' tcp client
    Private numClient As Integer    ' customer no
 
    ' manufacturer
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' we note the tcp client
        Me.client = client
        ' and its
        Me.numClient = numClient
    End Sub
 
    ' thread Run method
    Public Sub Run()
        ' local data
        Dim [IN] As StreamReader = Nothing        ' network read stream
        Dim réponse As String = Nothing        ' server response
        ' follow-up
        Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
        ' error management
        Try
            ' create network read stream
            [IN] = New StreamReader(client.GetStream())
            ' loop read text lines from IN stream
            While True
                ' network streaming
                réponse = [IN].ReadLine()
                ' closed flow?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' display
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' error
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' end - close flows
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' signals the end of the thread
        Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
    End Sub
End Class

9.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 كمعلمة ويعرض النص المرسل من الخادم على الشاشة. سنفترض أن الخادم يدعم بروتوكول HTTP 1.1. من الرؤوس المذكورة أعلاه، سنستخدم فقط ما يلي:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • يشير الرأس الأول إلى الصفحة التي نريدها
  • والثاني يحدد الخادم الذي نستعلم عنه
  • ويشير الثالث إلى أننا نريد من الخادم إغلاق الاتصال بعد الرد علينا.

إذا استبدلنا GET بـ HEAD في المثال أعلاه، فسيرسل لنا الخادم رؤوس HTTP فقط وليس صفحة HTML.

سيتم استدعاء عميل الويب الخاص بنا على النحو التالي: webclient URL cmd، حيث URL هو عنوان URL المطلوب و cmd هي إحدى الكلمتين الرئيسيتين GET أو HEAD للإشارة إلى ما إذا كنا نريد الرؤوس فقط (HEAD) أو محتوى الصفحة أيضًا (GET). لنلقِ نظرة على المثال الأول. نبدأ تشغيل خادم IIS ثم عميل الويب على نفس الجهاز:

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

الاستجابة

HTTP/1.1 302 Object moved

يعني أن الصفحة المطلوبة قد تم نقلها (وبالتالي أصبح لها عنوان URL جديد). يتم توفير عنوان URL الجديد من خلال رأس Location:

Location: /IISSamples/Default/welcome.htm

إذا استخدمنا GET بدلاً من HEAD في الاستدعاء إلى عميل الويب:

dos>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. البرنامج كالتالي:


' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
 
 
Public Class clientWeb1
 
    ' requests a URL
    ' displays its contents on the screen
    Public Shared Sub Main(ByVal args() As String)
        ' syntax
        Const syntaxe As String = "pg URI GET/HEAD"
 
        ' number of arguments
        If args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' note the URI required
        Dim URIstring As String = args(0)
        Dim commande As String = args(1).ToUpper()
 
        ' URI validity check
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI incorrect
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try
        ' order verification
        If commande <> "GET" And commande <> "HEAD" Then
            ' incorrect order
            erreur("Le second paramètre doit être GET ou HEAD", 3)
        End If
 
        ' we can work
        Dim client As TcpClient = Nothing        ' the customer
        Dim [IN] As StreamReader = Nothing        ' the customer's reading flow
        Dim OUT As StreamWriter = Nothing        ' the customer's writing flow
        Dim réponse As String = Nothing        ' server response
        Try
            ' connect to the server
            client = New TcpClient(uri.Host, uri.Port)
 
            ' create customer input/output flows TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
 
            ' request URL - send HTTP headers
            OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
            OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
            OUT.WriteLine("Connection: close")
            OUT.WriteLine()
            ' we read the answer
            réponse = [IN].ReadLine()
            While Not (réponse Is Nothing)
                ' the answer is processed
                Console.Out.WriteLine(réponse)
                ' we read the answer
                réponse = [IN].ReadLine()
            End While
            ' it's over
            client.Close()
        Catch e As Exception
            ' we handle the exception
            erreur(e.Message, 4)
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class

الميزة الجديدة الوحيدة في هذا البرنامج هي استخدام فئة Uri. يتلقى البرنامج عنوان URL (محدد موقع الموارد الموحد) أو URI (معرف الموارد الموحد) بالصيغة http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... تسمح لنا فئة Uri بتقسيم سلسلة URL إلى مكوناتها المختلفة. يتم إنشاء كائن Uri من سلسلة URI التي تم استلامها كمعلمة:


        ' URI validity check
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI incorrect
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

إذا كانت سلسلة URI المستلمة كمعلمة غير صالحة (بروتوكول مفقود، خادم مفقود، إلخ)، يتم إصدار استثناء. وهذا يسمح لنا بالتحقق من صحة المعلمة المستلمة. بمجرد إنشاء كائن URI، يمكننا الوصول إلى عناصره المختلفة. وبالتالي، إذا تم إنشاء كائن URI من الكود السابق من السلسلة http://serveur:port/cheminPageHTML?param1=val1;param2=val2;...، فسيكون لدينا:

uri.Host=server، uri.Port=port، uri.Path=HTMLPagePath، uri.Query=param1=val1;param2=val2;...، uri.pathAndQuery=HTMLPagePath?param1=val1;param2=val2;...، uri.Scheme=http.

9.4.6. معالجة عمليات إعادة التوجيه من قبل عميل الويب

لا يتعامل عميل الويب السابق مع أي إعادة توجيه محتملة لعنوان URL الذي طلبه. أما العميل التالي فيتعامل معها.

  1. يقرأ السطر الأول من رؤوس HTTP المرسلة من الخادم للتحقق مما إذا كان يحتوي على السلسلة "302 Object movedوالتي تشير إلى إعادة توجيه
  2. يقوم البرنامج بقراءة الرؤوس التالية. في حالة وجود إعادة توجيه، يبحث عن السطر "Location: url" الذي يوفر عنوان URL الجديد للصفحة المطلوبة ويقوم بتدوين هذا العنوان.
  3. يعرض بقية استجابة الخادم. إذا كان هناك إعادة توجيه، تتكرر الخطوات من 1 إلى 3 باستخدام عنوان URL الجديد. لا يقبل البرنامج أكثر من إعادة توجيه واحدة. يتم تحديد هذا الحد بواسطة ثابت يمكن تعديله.

فيما يلي مثال:

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

البرنامج كالتالي:


' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
 
' web client class
Public Class clientWeb
   
  ' requests a URL and displays its contents on the screen
  Public Shared Sub Main(ByVal args() As String)
    ' syntax
    Const syntaxe As String = "pg URI GET/HEAD"
 
    ' number of arguments
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' note the URI required
    Dim URIstring As String = args(0)
    Dim commande As String = args(1).ToUpper()
 
    ' URI validity check
    Dim uri As Uri = Nothing
    Try
      uri = New Uri(URIstring)
    Catch ex As Exception
      ' URI incorrect
      erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
    End Try 'catch
    ' order verification
    If commande <> "GET" And commande <> "HEAD" Then
      ' incorrect order
      erreur("Le second paramètre doit être GET ou HEAD", 3)
    End If
 
    ' we can work
    Dim client As TcpClient = Nothing ' the customer
    Dim [IN] As StreamReader = Nothing ' the customer's reading flow
    Dim OUT As StreamWriter = Nothing ' the customer's writing flow
    Dim réponse As String = Nothing ' server response
    Const nbRedirsMax As Integer = 1 ' no more than one redirection accepted
    Dim nbRedirs As Integer = 0 ' number of redirects in progress
    Dim premièreLigne As String ' 1st line of the answer
    Dim redir As Boolean = False ' indicates redirection or not
    Dim locationString As String = "" ' the URI string of a possible redirection
    ' regular expression to find a URL redirect
    Dim location As New Regex("^Location: (.+?)$") '
 
        ' error management
    Try
      ' you may have several URL to request if there are redirections
      While nbRedirs <= nbRedirsMax
        ' connect to the server
        client = New TcpClient(uri.Host, uri.Port)
 
        ' create customer input/output flows TCP
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True
 
        ' we send HTTP headers to request URL
        OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
        OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
        OUT.WriteLine("Connection: close")
        OUT.WriteLine()
 
        ' read the first line of the answer
        premièreLigne = [IN].ReadLine()
        ' screen echo
        Console.Out.WriteLine(premièreLigne)
 
        ' redirection?
        If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
          ' there is a redirection
          redir = True
          nbRedirs += 1
                End If
 
                ' next HTTP headers until you find the empty line signalling the end of the headers
                Dim locationFound As Boolean = False
                réponse = [IN].ReadLine()
                While réponse <> ""
                    ' the answer is displayed
                    Console.Out.WriteLine(réponse)
                    ' if there is a redirection, we search for the Location header
                    If redir And Not locationFound Then
                        ' compare the line with the relational expression location
                        Dim résultat As Match = location.Match(réponse)
                        If résultat.Success Then
                            ' if found, note the URL of redirection
                            locationString = résultat.Groups(1).Value
                            ' we note that we found
                            locationFound = True
                        End If
                    End If
                    ' next line
                    réponse = [IN].ReadLine()
                End While
 
                ' following lines of the answer
                Console.Out.WriteLine(réponse)
                réponse = [IN].ReadLine()
                While Not (réponse Is Nothing)
                    ' the answer is displayed
                    Console.Out.WriteLine(réponse)
                    ' next line
                    réponse = [IN].ReadLine()
                End While
 
                ' close the connection
                client.Close()
                ' are we done?
                If Not locationFound Or nbRedirs > nbRedirsMax Then
                    Exit While
                End If
 
                ' there is a redirection to be made - we build the new Uri
                URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
                uri = New Uri(URIstring)
                ' follow-up
                Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
            End While
        Catch e As Exception
      ' we handle the exception
      erreur(e.Message, 4)
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class

9.4.7. خادم حساب الضرائب

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


Public Class impôt
    ' les données nécessaires au calcul de l'impôt
    ' proviennent d'une source extérieure
    Private limites(), coeffR(), coeffN() as double

تحتوي الفئة على منشئين:

  • منشئ يأخذ مصفوفات البيانات الثلاثة اللازمة لحساب الضريبة
    // constructeur 1
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' initializes the three limit arrays, coeffR, coeffN from
        ' parameters passed to the constructor
  • منشئ نمرر إليه اسم DSN لقاعدة بيانات ODBC
    ' builder 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' initializes the three limit arrays, coeffR, coeffN from
        ' the contents of the Timpots table in the ODBC DSNimpots database
        ' colLimites, colCoeffR, colCoeffN are the three columns of this table
        ' can throw an exception

تمت كتابة برنامج اختبار:

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F

هنا، كان برنامج الاختبار وكائن الضريبة على نفس الجهاز. نقترح وضع برنامج الاختبار وكائن الضريبة على أجهزة مختلفة. سيكون لدينا تطبيق خادم-عميل حيث سيكون كائن الضريبة البعيد هو الخادم. تسمى الفئة الجديدة TaxServer وهي مشتقة من فئة الضريبة:


Public Class ServeurImpots
    Inherits impôt
 
    ' attributes
    Private portEcoute As Integer    ' the ability to listen to customer requests
    Private actif As Boolean    ' server status
 
    ' manufacturer
    Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
        ' we note the listening port
        Me.portEcoute = portEcoute
        ' currently inactive
        actif = False
        ' creates and launches a thread for reading keyboard commands
        ' the server will be managed using these commands
        Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
        threadLecture.Start()
    End Sub

المعلمة الجديدة الوحيدة في مُنشئ الكائن هي المنفذ المخصص للاستماع إلى طلبات العملاء. أما المعلمات الأخرى، فيتم تمريرها مباشرةً إلى فئة الضرائب الأساسية. ويتم التحكم في خادم الضرائب عن طريق أوامر لوحة المفاتيح. ولذلك، نقوم بإنشاء مؤشر ترابط لقراءة هذه الأوامر. وسيكون هناك أمران محتملان: الأمر «start» لتشغيل الخدمة، والأمر «stop» لإيقافها نهائيًا. وفيما يلي طريقة «admin» التي تتولى معالجة هذه الأوامر:


    Public Sub admin()
        ' reads server administration commands typed from the keyboard
        ' in an endless loop
        Dim commande As String = Nothing
        While True
            ' invite
            Console.Out.Write("Serveur d'impôts>")
            ' read command
            commande = Console.In.ReadLine().Trim().ToLower()
            ' order execution
            If commande = "start" Then
                ' active?
                If actif Then
                    'error
                    Console.Out.WriteLine("Le serveur est déjà actif")
                Else
                    ' we launch the listening service
                    Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
                    threadEcoute.Start()
                End If
            Else
                If commande = "stop" Then
                    ' end of all execution threads
                    Environment.Exit(0)
                Else
                    ' error
                    Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
                End If
            End If
        End While
    End Sub

إذا كان الأمر الذي تم إدخاله عبر لوحة المفاتيح هو "start"، يتم تشغيل مؤشر ترابط يستمع لطلبات العميل. إذا كان الأمر الذي تم إدخاله هو "stop"، يتم إيقاف جميع مؤشرات الترابط. يقوم مؤشر الترابط المستمع بتنفيذ طريقة "ecoute":


    Public Sub ecoute()
        ' thread for listening to customer requests
        ' we create the listening service
        Dim ecoute As TcpListener = Nothing
        Try
            ' create the service
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
            ' launch it
            ecoute.Start()
            ' follow-up
            Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
 
            ' service loop
            Dim liaisonClient As TcpClient = Nothing
            While True            ' infinite loop
                ' waiting for a customer
                liaisonClient = ecoute.AcceptTcpClient()
                ' the service is provided by another task
                Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
                threadClient.Start()
            End While
            ' back to listening to requests
        Catch ex As Exception
            ' we report the error
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub

لدينا هنا خادم TCP كلاسيكي يستمع على المنفذ portEcoute. تتم معالجة طلبات العميل بواسطة طريقة Run الخاصة بكائن يتم تمرير معلمتين إليه:

  1. كائن TcpClient، الذي يسمح بالوصول إلى العميل
  2. كائن الضريبة this، الذي يوفر الوصول إلى طريقة حساب الضريبة this.calculate.

' -------------------------------------------------------
' provides service to a tax server client
Public Class traiteClientImpots
 
    Private liaisonClient As TcpClient    ' customer liaison
    Private [IN] As StreamReader    ' iNPUTS
    Private OUT As StreamWriter    ' output flow
    Private objImpôt As impôt     ' object Tax
 
    ' manufacturer
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
        Me.liaisonClient = liaisonClient
        Me.objImpôt = objImpôt
    End Sub

تقوم طريقة Run بمعالجة طلبات العملاء. ويمكن أن تتخذ هذه الطلبات شكلين:

  1. calculateMarried(y/n) numChildren annualSalary
  2. إنهاء الحسابات

النموذج 1 يحسب الضريبة، بينما النموذج 2 يغلق اتصال العميل بالخادم.


    ' run method
    Public Sub Run()
        ' renders service to the customer
        Try
            ' iNPUTS
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' output flow
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' send a welcome message to the customer
            OUT.WriteLine("Bienvenue sur le serveur d'impôts")
 
            ' loop read request/write response
            Dim demande As String = Nothing
            Dim champs As String() = Nothing            ' elements of the request
            Dim commande As String = Nothing            ' customer order: calculation or fincalculs
            demande = [IN].ReadLine()
            While Not (demande Is Nothing)
                ' demand is broken down into fields
                champs = Regex.Split(demande.Trim().ToLower(), "\s+")
                ' two successful applications: calcul and fincalculs
                commande = champs(0)
                Dim erreur As Boolean = False
                If commande <> "calcul" And commande <> "fincalculs" Then
                    ' customer error
                    OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
                End If
                If commande = "calcul" Then
                    calculerImpôt(champs)
                End If
                If commande = "fincalculs" Then
                    ' good-bye message to customer
                    OUT.WriteLine("Au revoir...")
                    ' freeing up resources
                    Try
                        OUT.Close()
                        [IN].Close()
                        liaisonClient.Close()
                    Catch
                    End Try
                    ' end
                    Return
                End If
                ' new request
                demande = [IN].ReadLine()
            End While
        Catch e As Exception
            erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
        End Try
    End Sub

يتم حساب الضريبة بواسطة طريقة CalculateTax، التي تأخذ مصفوفة الحقول من طلب العميل كمعلمة. يتم التحقق من صحة الطلب، وإذا كان صحيحًا، يتم حساب الضريبة وإرجاعها إلى العميل.


    ' tax calculation
    Public Sub calculerImpôt(ByVal champs() As String)
        ' processing the application: calculation married nbEnfants salaireAnnuel
        ' broken down into fields in the fields table
        Dim marié As String = Nothing
        Dim nbEnfants As Integer = 0
        Dim salaireAnnuel As Integer = 0
 
        ' validity of arguments
        Try
            ' at least 4 fields are required
            If champs.Length <> 4 Then
                Throw New Exception
            End If
            ' married
            marié = champs(1)
            If marié <> "o" And marié <> "n" Then
                Throw New Exception
            End If
            ' children
            nbEnfants = Integer.Parse(champs(2))
            ' salary
            salaireAnnuel = Integer.Parse(champs(3))
        Catch
            OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
            ' finish
            Exit Sub
        End Try
        ' tax can be calculated
        Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
        ' we send the response to the client
        OUT.WriteLine(impot.ToString)
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub

تم ترجمة هذه الفئة بواسطة

dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

حيث تحتوي impots.dll على كود فئة impôt. قد يبدو برنامج الاختبار كما يلي:


' namespaces
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
 
Public Class testServeurImpots
    Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
 
    ' main program
    Public Shared Sub Main(ByVal args() As String)
 
        ' you need6 arguments
        If args.Length <> 6 Then
            erreur(syntaxe, 1)
        End If
        ' port must be integer >0
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' we create the tax server
        Try
            Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
        Catch ex As Exception
            'error
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
    End Sub
 
    ' error display
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' error display
        System.Console.Error.WriteLine(msg)
        ' stop with error
        Environment.Exit(exitCode)
    End Sub
End Class

نقوم بتمرير البيانات المطلوبة لإنشاء كائن ServeurImpots إلى برنامج الاختبار، ومن هناك يقوم البرنامج بإنشاء هذا الكائن. يتم ترجمة برنامج الاختبار هذا بواسطة:

dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

إليك اختبار أولي:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

السطر

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

يُنشئ كائن TaxServer الذي لا يستمع بعد لطلبات العملاء. إن الأمر start الذي يتم كتابته على لوحة المفاتيح هو الذي يبدأ عملية الاستماع هذه. أما الأمر stop فيوقف الخادم. لنستخدم الآن عميلاً. سنستخدم العميل العام الذي تم إنشاؤه سابقاً. يتم تشغيل الخادم:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

يتم تشغيل العميل العام في نافذة DOS أخرى:

dos> clttcpgenerique localhost 124Commandes :
<-- Bienvenue sur le serveur d'impôts

يمكننا أن نرى أن العميل قد تلقى رسالة الترحيب من الخادم بنجاح. نرسل أوامر أخرى:

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>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop