9. البرمجة TCP-IP
9.1. معلومات عامة
9.1.1. بروتوكولات الإنترنت
نقدم هنا مقدمة لبروتوكولات الاتصال على الإنترنت، والتي تُعرف أيضًا باسم مجموعة بروتوكولات TCP/IP (بروتوكول التحكم في النقل / بروتوكول الإنترنت)، نسبةً إلى اسمي البروتوكولين الرئيسيين. من المستحسن أن يكون لدى القارئ فهم شامل لكيفية عمل الشبكات، ولا سيما بروتوكولي TCP/IP، قبل الشروع في بناء التطبيقات الموزعة.
النص التالي هو ترجمة جزئية لنص موجود في وثيقة «Lan Workplace for Dos - Administrator's Guide» الصادرة عن NOVELL، وهي وثيقة تعود إلى أوائل التسعينيات.
يعود المفهوم العام لإنشاء شبكة من أجهزة الكمبيوتر غير المتجانسة إلى الأبحاث التي أجرتها وكالة مشاريع الأبحاث المتقدمة للدفاع (DARPA) في الولايات المتحدة. وقد طورت الوكالة مجموعة البروتوكولات المعروفة باسم TCP/IP، والتي تتيح لأجهزة غير متجانسة التواصل فيما بينها. وقد تم اختبار هذه البروتوكولات على شبكة تُسمى ARPAnet، وهي الشبكة التي أصبحت لاحقًا شبكة INTERNET. تحدد بروتوكولات TCP/IP تنسيقات وقواعد الإرسال والاستقبال التي لا تعتمد على تنظيم الشبكات أو الأجهزة المستخدمة.
الشبكة التي صممها DARPA وتديرها بروتوكولات TCP/IP هي شبكة تعمل بنظام تبديل الحزم. تقوم هذه الشبكة بنقل المعلومات عبر الشبكة على شكل أجزاء صغيرة تُسمى الحزم. وبالتالي، إذا قام جهاز كمبيوتر بإرسال ملف كبير الحجم، فسيتم تقسيمه إلى أجزاء صغيرة تُرسل عبر الشبكة ليتم إعادة تجميعها عند الوجهة. يحدد TCP/IP تنسيق هذه الحزم، أي:
- منشأ الحزمة
- الوجهة
- الطول
- النوع
9.1.2. النموذج OSI
تتبع بروتوكولات TCP/IP بشكل عام نموذج الشبكة المفتوحة المسمى OSI (نموذج مرجعي لترابط الأنظمة المفتوحة) الذي حددته منظمة ISO (منظمة المعايير الدولية). يصف هذا النموذج شبكةً مثاليةً يمكن فيها تمثيل الاتصال بين الأجهزة بنموذج مكون من سبع طبقات:
![]() |
تتلقى كل طبقة الخدمات من الطبقة السفلية وتقدم خدماتها إلى الطبقة العليا. لنفترض أن تطبيقين موجودين على جهازين مختلفين، A و B، يرغبان في التواصل: فيقومان بذلك على مستوى الطبقة Application. ولا يحتاجان إلى معرفة كل تفاصيل عمل الشبكة: فكل تطبيق يسلم المعلومات التي يرغب في إرسالها إلى الطبقة التي تليه، وهي الطبقة Présentation. وبالتالي، لا يتعين على التطبيق سوى معرفة قواعد التفاعل مع الطبقة Présentation.
وبمجرد وصول المعلومات إلى الطبقة Présentation، يتم تمريرها وفقًا لقواعد أخرى إلى الطبقة Session وهكذا دواليك، حتى تصل المعلومات إلى الوسيط المادي ويتم إرسالها فعليًّا إلى الجهاز المقصود. وهناك، ستخضع لعملية معالجة عكسية لتلك التي خضعت لها على الجهاز المرسل.
في كل طبقة، تقوم العملية المرسلة المكلفة بإرسال المعلومات بإرسالها إلى عملية مستقبلة على الجهاز الآخر الذي ينتمي إلى نفس الطبقة التي تنتمي إليها. وتقوم بذلك وفقًا لقواعد معينة تُسمى بروتوكول الطبقة. وبذلك نحصل على مخطط الاتصال النهائي التالي:
![]() |
دور الطبقات المختلفة هو كما يلي:
تضمن نقل البتات عبر وسيط مادي. توجد في هذه الطبقة أجهزة طرفية لمعالجة البيانات (E.T.T.D.) مثل المحطة الطرفية أو الكمبيوتر، بالإضافة إلى أجهزة إنهاء دوائر البيانات (E.T.C.D.) مثل المُعدِّل/المُفكِّك، والمُضاعِف، والمُركِّز. النقاط المهمة في هذا المستوى هي: . اختيار طريقة ترميز المعلومات (تناظرية أو رقمية) . اختيار طريقة الإرسال (متزامنة أو غير متزامنة). | |
يخفي الخصائص الفيزيائية للطبقة المادية. يكتشف أخطاء الإرسال ويصححها. | |
يدير المسار الذي يجب أن تتبعه المعلومات المرسلة عبر الشبكة. ويُطلق على ذلك اسم routage: تحديد المسار الذي يجب أن تتبعه المعلومات حتى تصل إلى مستلمها. | |
تسمح بالاتصال بين تطبيقين، في حين أن الطبقات السابقة كانت تسمح فقط بالاتصال بين الأجهزة. ومن الخدمات التي توفرها هذه الطبقة التعدد: حيث يمكن لطبقة النقل استخدام نفس اتصال الشبكة (من جهاز إلى جهاز) لنقل المعلومات الخاصة بعدة تطبيقات. | |
نجد في هذه الطبقة خدمات تسمح لأحد التطبيقات بفتح جلسة عمل والحفاظ عليها على جهاز بعيد. | |
وتهدف هذه الطبقة إلى توحيد عرض البيانات على الأجهزة المختلفة. وبالتالي، فإن البيانات الواردة من الجهاز «أ» سيتم «تنسيقها» بواسطة طبقة Présentation الخاصة بالجهاز «أ»، وفقًا لتنسيق قياسي قبل إرسالها عبر الشبكة. وعند وصولها إلى الطبقة Présentation في الجهاز المستلم B، الذي سيتعرف عليها بفضل تنسيقها القياسي، سيتم تكييفها بطريقة أخرى حتى يتعرف عليها تطبيق الجهاز B. | |
في هذه المرحلة، نجد التطبيقات التي عادةً ما تكون قريبة من المستخدم، مثل البريد الإلكتروني أو نقل الملفات. |
9.1.3. النموذج TCP/IP
النموذج OSI هو نموذج مثالي لم يتم تنفيذه بعد. تقترب مجموعة البروتوكولات TCP/IP منه بالشكل التالي:
![]() |
الطبقة المادية
في الشبكات المحلية، تُستخدم عادةً تقنية إيثرنت أو توكن رينغ. ونحن نقتصر هنا على عرض تقنية إيثرنت.
إيثرنت
هذا هو الاسم الذي أُطلق على تقنية الشبكات المحلية ذات التبديل الحزمي التي اخترعتها شركة Xerox في أوائل السبعينيات، وتم توحيد معاييرها من قبل شركات Xerox وIntel وDigital Equipment في عام 1978. تتكون الشبكة ماديًا من كابل متحد المحور يبلغ قطره حوالي 1,27 سم وطوله 500 متر كحد أقصى. ويمكن تمديدها باستخدام répéteurs، بحيث لا يمكن أن يفصل بين جهازين أكثر من مكررين. الكابل غير نشط: فجميع العناصر النشطة موجودة في الأجهزة المتصلة بالكابل. وترتبط كل جهاز بالكابل عن طريق بطاقة وصول إلى الشبكة تتضمن:
- جهاز إرسال (transceiver) يكتشف وجود الإشارات على الكابل ويحول الإشارات التناظرية إلى إشارات رقمية والعكس.
- مقرن يستقبل الإشارات الرقمية من جهاز الإرسال وينقلها إلى الكمبيوتر للمعالجة أو العكس.
الخصائص الرئيسية لتقنية إيثرنت هي كما يلي:
- سعة تبلغ 10 ميغابت/ثانية.
- هيكل شبكي على شكل ناقل (bus): جميع الأجهزة متصلة بنفس الكابل
![]() |
- شبكة البث - تقوم الجهاز المرسل بنقل المعلومات عبر الكابل مع عنوان الجهاز المستلم. ثم تتلقى جميع الأجهزة المتصلة هذه المعلومات، ولا يحتفظ بها سوى الجهاز الذي وجهت إليه.
- طريقة الوصول هي كما يلي: يقوم جهاز الإرسال الراغب في الإرسال بالاستماع إلى الكابل — فيكتشف عندئذٍ وجود موجة حاملة أم لا، حيث يعني وجودها أن عملية إرسال جارية. هذه هي تقنية CSMA (الوصول المتعدد باستشعار الموجة الحاملة). في حالة عدم وجود موجة حاملة، يمكن لجهاز الإرسال أن يقرر الإرسال بدوره. وقد يتخذ عدة أجهزة هذا القرار في آن واحد. فتتداخل الإشارات المرسلة: ويُقال عندئذٍ إن هناك تضاربًا. يكتشف جهاز الإرسال هذه الحالة: ففي الوقت الذي يبث فيه عبر الكابل، يستمع في الوقت نفسه إلى ما يمر فعليًّا عبره. وإذا اكتشف أن المعلومات التي تمر عبر الكابل ليست هي التي أرسلها، يستنتج أن هناك تصادمًا ويتوقف عن الإرسال. وستفعل أجهزة الإرسال الأخرى التي كانت تبث الشيء نفسه. سيستأنف كل جهاز إرساله بعد فترة زمنية عشوائية تعتمد على كل جهاز إرسال. تُسمى هذه التقنية CD (Collision Detect). وبالتالي تُسمى طريقة الوصول CSMA/CD.
- عنونة على 48 بت. لكل جهاز عنوان، يُسمى هنا العنوان المادي، وهو مسجل على البطاقة التي تربطه بالكابل. يُطلق على هذا العنوان اسم عنوان Ethernet للجهاز.
طبقة الشبكة
نجد في هذه الطبقة البروتوكولات IP و ICMP و ARP و RARP.
تنقل الحزم بين عقدتين في الشبكة | |
يقوم ICMP بإجراء الاتصال بين برنامج بروتوكول IP الخاص بجهاز ما وبرنامج بروتوكول IP الخاص بجهاز آخر. وهو بالتالي بروتوكول لتبادل الرسائل داخل بروتوكول IP نفسه. | |
يقوم بمطابقة عنوان الإنترنت الخاص بالجهاز مع العنوان الفعلي للجهاز | |
يقوم بمطابقة العنوان الفعلي للجهاز مع عنوان الإنترنت الخاص به |
طبقات النقل/الجلسة
في هذه الطبقة، نجد البروتوكولات التالية:
يضمن التسليم الموثوق للمعلومات بين عميلين | |
يضمن التسليم غير الموثوق للمعلومات بين عميلين |
طبقات التطبيق/العرض/الجلسة
توجد هنا بروتوكولات متنوعة:
محاكي طرفي يسمح للجهاز «أ» بالاتصال بالجهاز «ب» باعتباره طرفًا | |
يسمح بنقل الملفات | |
يسمح بنقل الملفات | |
يسمح بتبادل الرسائل بين مستخدمي الشبكة | |
يحول اسم الجهاز إلى عنوان الإنترنت الخاص به | |
تم إنشاؤه بواسطة sun MicroSystems، ويحدد تمثيلاً قياسيًا للبيانات، مستقلًا عن الأجهزة | |
تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يعفي المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR | |
الذي حددته شركة Sun أيضًا، ويتيح هذا البروتوكول لجهاز ما «رؤية» نظام الملفات الخاص بجهاز آخر. وهو يعتمد على البروتوكول السابق RPC |
9.1.4. كيفية عمل بروتوكولات الإنترنت
عادةً ما تستخدم التطبيقات المطورة في بيئة TCP/IP العديد من بروتوكولات هذه البيئة. يتواصل برنامج التطبيق مع الطبقة العليا من البروتوكولات. تقوم هذه الطبقة بتمرير المعلومات إلى الطبقة التي تليها، وهكذا دواليك حتى تصل إلى الوسيط المادي. وهناك، تُنقل المعلومات فعليًّا إلى الجهاز المستلم حيث تعبر نفس الطبقات مرة أخرى، ولكن في الاتجاه المعاكس هذه المرة، حتى تصل إلى التطبيق المستلم للمعلومات المرسلة. يوضح الرسم التخطيطي التالي مسار المعلومات:
![]() |
لنأخذ مثالاً: التطبيق FTP، المُعرَّف على مستوى الطبقة Application والذي يتيح نقل الملفات بين الأجهزة.
- يُصدر التطبيق سلسلة من البايتات ليتم إرسالها إلى الطبقة transport.
- يقوم الطبقة transport بتقسيم سلسلة البايتات هذه إلى segments وTCP، ويضيف رقم كل مقطع في بداية كل مقطع. يتم تمرير المقاطع إلى طبقة الشبكة التي يحكمها البروتوكول IP.
- تقوم طبقة IP بإنشاء حزمة تغلف المقطع TCP المستلم. وتضع في رأس هذه الحزمة عناوين الإنترنت للجهازين المصدر والوجهة. كما تحدد العنوان المادي للجهاز المستلم. ثم يتم تمرير كل ذلك إلى طبقة ربط البيانات والربط المادي، أي إلى بطاقة الشبكة التي تربط الجهاز بالشبكة المادية.
- وهناك، يتم تغليف الحزمة IP بدورها في إطار مادي وإرسالها إلى المستلم عبر الكابل.
- على الجهاز المستلم، تقوم طبقة «الربط بين البيانات والربط المادي» بالعملية العكسية: فهي تفك تغليف الحزمة IP من الإطار المادي وتمررها إلى طبقة IP.
- تتحقق الطبقة IP من صحة الحزمة: فهي تحسب مجموعًا يعتمد على البتات المستلمة (checksum)، وهو المجموع الذي يجب أن تجده في رأس الحزمة. وإذا لم يكن الأمر كذلك، يتم رفض الحزمة.
- وإذا تم إقرار صحة الحزمة، تقوم الطبقة IP بفك تغليف المقطع TCP الموجود فيها وتمريره إلى الطبقة الأعلى transport.
- تقوم الطبقة transport، وهي الطبقة TCP في مثالنا، بفحص رقم المقطع من أجل استعادة الترتيب الصحيح للمقاطع.
- كما تقوم بحساب مجموع التحقق للقطعة TCP. إذا تبين أنه صحيح، ترسل الطبقة TCP إشعارًا بالاستلام إلى الجهاز المصدر، وإلا يتم رفض القطعة TCP.
- ولم يتبقَ للطبقة TCP سوى إرسال جزء البيانات من المقطع إلى التطبيق المستلم لهذه البيانات في الطبقة الأعلى.
9.1.5. التوجيه في الإنترنت
يمكن أن يكون noeud في شبكة ما جهاز كمبيوتر، أو طابعة ذكية، أو خادم ملفات، أو أي شيء في الواقع يمكنه الاتصال باستخدام بروتوكولات TCP/IP. لكل عقدة عنوان مادي يتخذ شكلًا يعتمد على نوع الشبكة. في شبكة إيثرنت، يتم ترميز العنوان المادي على 6 بايت. أما عنوان شبكة X25 فهو رقم مكون من 14 رقمًا.
عنوان الإنترنت الخاص بالعقدة هو عنوان منطقي: فهو مستقل عن الأجهزة والشبكة المستخدمة. وهو عنوان مكون من 4 بايت يحدد في آن واحد شبكة محلية وعقدة ضمن هذه الشبكة. وعادةً ما يُعبَّر عن عنوان الإنترنت في شكل 4 أرقام، تمثل قيم البايتات الأربعة، مفصولة بنقطة. وبالتالي، يُكتب عنوان الجهاز «Lagaffe» التابع لكلية العلوم في أنجيه على النحو التالي: 193.49.144.1، وعنوان الجهاز «Liny» على النحو التالي: 193.49.144.9. ومن ذلك يمكن استنتاج أن عنوان الإنترنت للشبكة المحلية هو 193.49.144.0. ويمكن أن تضم هذه الشبكة ما يصل إلى 254 عقدة.
ونظرًا لأن عناوين الإنترنت أو عناوين IP مستقلة عن الشبكة، يمكن لجهاز في الشبكة «أ» أن يتواصل مع جهاز في الشبكة «ب» دون الاهتمام بنوع الشبكة التي يوجد عليها: يكفي أن يعرف عنوانه 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 | هو عنوان جهاز في هذه الشبكة |
وبشكل أكثر دقة، فإن صيغة عنوان من الفئة A مثل IP هي كما يلي:
![]() |
يتكون عنوان الشبكة من 7 بتات، بينما يتكون عنوان العقدة من 24 بتة. وبالتالي، يمكن أن يكون هناك 127 شبكة من الفئة A، تضم كل منها ما يصل إلى 2²⁴ عقدة.
الفئة ب
هنا، العنوان IP: I1.I2.I3.I4 يتخذ الشكل R1.R2.N1.N2 حيث
R1.R2 | هو عنوان الشبكة |
N1.N2 | هو عنوان جهاز في هذه الشبكة |
وبشكل أكثر دقة، فإن صيغة عنوان من الفئة B مثل IP هي كما يلي:
![]() |
يتكون عنوان الشبكة من 2 بايت (14 بت بالضبط) وكذلك عنوان العقدة. وبالتالي، يمكن أن يكون هناك 2¹⁴ شبكة من الفئة B، كل منها تضم ما يصل إلى 2¹⁶ عقدة.
الفئة C
في هذه الفئة، يتخذ العنوان IP: I1.I2.I3.I4 الشكل R1.R2.R3.N1 حيث
R1.R2.R3 | هو عنوان الشبكة |
N1 | هو عنوان جهاز في هذه الشبكة |
وبشكل أكثر دقة، فإن صيغة عنوان من الفئة C مثل IP هي كما يلي:
![]() |
يتكون عنوان الشبكة من 3 بايت (ناقص 3 بتات) وعنوان العقدة من 1 بايت. وبالتالي، يمكن أن يكون هناك 221 شبكة من الفئة C تضم ما يصل إلى 256 عقدة.
بما أن عنوان الجهاز Lagaffe التابع لكلية العلوم في أنجيه هو 193.49.144.1، نلاحظ أن البايت الأعلى قيمة يساوي 193، أي 11000001 بالصيغة الثنائية. ونستنتج من ذلك أن الشبكة من الفئة C.
العناوين المحجوزة
- بعض العناوين مثل IP هي عناوين شبكات وليست عناوين عقد في الشبكة. وهي تلك العناوين التي يكون فيها عنوان العقدة 0. وهكذا، فإن العنوان 193.49.144.0 هو عنوان الشبكة IP الخاص بكلية العلوم في أنجيه. وبالتالي، لا يمكن لأي عقدة في الشبكة أن يكون عنوانها صفرًا.
- وعندما لا يحتوي عنوان العقدة في عنوان مثل IP إلا على أرقام 1، يكون لدينا عندئذٍ عنوان بث: يشير هذا العنوان إلى جميع العقد في الشبكة.
- في شبكة من الفئة C، التي تسمح نظريًّا بـ 28=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. ويجب أن يكون لهذا الملف عادةً عنوان ثابت هو IP، والذي يجب أن يكون قادراً على معرفته دون الحاجة إلى استخدام بروتوكول RARP بنفسه.
9.1.6. طبقة الشبكة المعروفة باسم طبقة IP للإنترنت
يحدد بروتوكول IP (بروتوكول الإنترنت) الشكل الذي يجب أن تتخذه الحزم والطريقة التي يجب أن تُدار بها عند إرسالها أو استلامها. يُطلق على هذا النوع المحدد من الحزم اسم «داتاغرام» IP. وقد سبق أن عرضنا ذلك:
![]() |
المهم هو أنه بالإضافة إلى البيانات المراد إرسالها، تحتوي داتاغرام IP على عناوين الإنترنت للجهاز المرسل والجهاز المستقبل. وبذلك يعرف الجهاز المستقبل من الذي يرسل إليه الرسالة.
وعلى عكس الإطار الشبكي الذي يحدد طوله وفقًا للخصائص الفيزيائية للشبكة التي يمر عبرها، فإن طول داتاغرام IP يحدده البرنامج، وبالتالي سيكون هو نفسه عبر شبكات فيزيائية مختلفة. لقد رأينا أنه عند النزول من طبقة الشبكة إلى الطبقة المادية، يتم تغليف داتاغرام IP في إطار مادي. وقد قدمنا مثالاً على الإطار المادي لشبكة إيثرنت:
![]() |
تنتقل الإطارات المادية من عقدة إلى أخرى نحو وجهتها التي قد لا تكون على نفس الشبكة المادية التي توجد عليها الجهاز المرسل. وبالتالي، يمكن تغليف الحزمة IP تباعًا في إطارات مادية مختلفة عند العقد التي تشكل الوصلة بين شبكتين من نوعين مختلفين. كما قد تكون الحزمة IP أكبر من أن يتم تغليفها في إطار مادي. وعندئذٍ يقوم البرنامج IP في العقدة التي تحدث فيها هذه المشكلة بتقسيم الحزمة IP إلى fragments وفقًا لقواعد محددة، ثم يتم إرسال كل منها عبر الشبكة المادية. ولن يتم إعادة تجميعها إلا عند وصولها إلى وجهتها النهائية.
9.1.6.1. التوجيه
التوجيه هو طريقة توجيه الحزم IP إلى وجهتها. وهناك طريقتان: التوجيه المباشر والتوجيه غير المباشر.
التوجيه المباشر
يشير التوجيه المباشر إلى توجيه حزمة IP مباشرةً من المرسل إلى المستلم داخل الشبكة نفسها:
- الجهاز المرسل لحزمة البيانات IP لديه عنوان المستلم IP.
- وتحصل على العنوان المادي للمستلم عبر بروتوكول ARP أو من جداولها، إذا كان هذا العنوان قد تم الحصول عليه مسبقًا.
- ثم ترسل الحزمة عبر الشبكة إلى هذا العنوان المادي.
التوجيه غير المباشر
يشير التوجيه غير المباشر إلى توجيه حزمة IP إلى وجهة تقع على شبكة أخرى غير تلك التي ينتمي إليها المرسل. في هذه الحالة، تختلف أجزاء عنوان الشبكة في عناوين IP لكل من الجهاز المرسل والجهاز المستقبل. يدرك الجهاز المرسل هذه النقطة. ثم ترسل الحزمة إلى عقدة خاصة تُسمى جهاز التوجيه (router)، وهي عقدة تربط شبكة محلية بشبكات أخرى، وتجد جهاز التوجيه عنوانها (IP) في جداوله، وهو عنوان تم الحصول عليه مسبقًا إما من ملف أو من ذاكرة دائمة أو حتى عبر المعلومات المتداولة على الشبكة.
يكون جهاز التوجيه متصلاً بشبكتين وله عنوان IP داخل هاتين الشبكتين.
![]() |
في المثال أعلاه:
- الشبكة رقم 1 لها عنوان الإنترنت 193.49.144.0 والشبكة رقم 2 لها العنوان 193.49.145.0.
- داخل الشبكة رقم 1، يمتلك جهاز التوجيه العنوان 193.49.144.6، ويمتلك العنوان 193.49.145.3 داخل الشبكة رقم 2.
تتمثل مهمة جهاز التوجيه في تحويل الحزمة IP التي يستقبلها، والموجودة في إطار مادي نموذجي للشبكة رقم 1، إلى إطار مادي يمكن أن ينتقل عبر الشبكة رقم 2. إذا كان عنوان IP الخاص بمستلم الحزمة موجودًا في الشبكة رقم 2، فسيرسل الموجه الحزمة إليه مباشرةً، وإلا فسيرسلها إلى موجه آخر يربط الشبكة رقم 2 بشبكة رقم 3 وهكذا دواليك.
9.1.6.2. رسائل الخطأ والتحكم
وفي طبقة الشبكة نفسها، أي على نفس مستوى بروتوكول IP، يوجد بروتوكول ICMP (بروتوكول رسائل التحكم في الإنترنت). ويُستخدم هذا البروتوكول لإرسال رسائل تتعلق بالتشغيل الداخلي للشبكة: العقد المعطلة، الازدحام عند أحد أجهزة التوجيه، وما إلى ذلك... يتم تغليف رسائل ICMP في حزم IP وإرسالها عبر الشبكة. تتخذ طبقات IP في العقد المختلفة الإجراءات المناسبة وفقًا لرسائل ICMP التي تتلقاها. وبالتالي، لا يرى التطبيق نفسه أبدًا هذه المشكلات الخاصة بالشبكة. ستستخدم العقدة المعلومات ICMP لتحديث جداول التوجيه الخاصة بها.
9.1.7. طبقة النقل: بروتوكولا UDP و TCP
9.1.7.1. بروتوكول UDP: بروتوكول مخطط بيانات المستخدم (User Datagram Protocol)
يسمح بروتوكول UDP بتبادل البيانات غير الموثوق به بين نقطتين، أي أن التوجيه الصحيح لحزمة البيانات إلى وجهتها غير مضمون. ويمكن للتطبيق، إذا رغب في ذلك، إدارة هذا الأمر بنفسه، على سبيل المثال بالانتظار بعد إرسال رسالة ما حتى تلقي إشعار بالاستلام قبل إرسال الرسالة التالية.
في الوقت الحالي، على مستوى الشبكة، تحدثنا عن عناوين IP للأجهزة. لكن على جهاز واحد، يمكن أن تتواجد في الوقت نفسه عمليات مختلفة يمكنها جميعًا التواصل مع بعضها البعض. لذلك، عند إرسال رسالة، يجب الإشارة ليس فقط إلى عنوان الجهاز المستلم IP، بل أيضًا إلى «اسم» العملية المستلمة. هذا الاسم هو في الواقع رقم يُسمى رقم المنفذ. بعض الأرقام محجوزة للتطبيقات القياسية: المنفذ 69 لتطبيق tftp (بروتوكول نقل الملفات البسيط) على سبيل المثال. تُسمى الحزم التي يديرها بروتوكول UDP أيضًا داتاغرامات. وهي تأخذ الشكل التالي:
![]() |
سيتم تغليف هذه الداتاغرامات في حزم IP، ثم في إطارات مادية.
9.1.7.2. بروتوكول TCP: بروتوكول التحكم في النقل
لضمان أمان الاتصالات، لا يكفي بروتوكول UDP: يجب على مطور التطبيقات أن يضع بنفسه بروتوكولًا يتيح له التحقق من التوجيه الصحيح للحزم.
يُجنّب بروتوكول TCP (بروتوكول التحكم في النقل) هذه المشاكل. وتتمثل خصائصه فيما يلي:
- تقوم العملية التي ترغب في الإرسال أولاً بإنشاء اتصال مع العملية المستقبلة للمعلومات التي سترسلها. يتم هذا الاتصال بين منفذ في الجهاز المرسل ومنفذ في الجهاز المستقبل. وبذلك يتم إنشاء مسار افتراضي بين المنفذين، ويكون هذا المسار مخصصاً حصرياً للعمليتين اللتين قامتا بإجراء الاتصال.
- تتبع جميع الحزم المرسلة من العملية المصدر هذا المسار الافتراضي وتصل بالترتيب الذي أُرسلت به، وهو ما لم يكن مضمونًا في بروتوكول UDP حيث كان من الممكن أن تتبع الحزم مسارات مختلفة.
- تتسم المعلومات المرسلة بطابع مستمر. ترسل العملية المرسلة المعلومات وفقًا لإيقاعها الخاص. ولا يتم إرسال هذه المعلومات بالضرورة على الفور: فبروتوكول TCP ينتظر حتى يتوفر لديه ما يكفي منها لإرسالها. ويتم تخزينها في بنية تُسمى المقطع TCP. وبمجرد امتلاء هذا المقطع، سيتم إرساله إلى الطبقة IP حيث سيتم تغليفه في حزمة IP.
- يتم ترقيم كل مقطع يتم إرساله عبر بروتوكول TCP. ويتحقق بروتوكول TCP المستلم من استلام المقاطع بالترتيب الصحيح. وبالنسبة لكل مقطع يتم استلامه بشكل صحيح، يرسل إشعارًا بالاستلام إلى المرسل.
- وعندما يتلقى المرسل هذا الإقرار، يقوم بإبلاغ العملية المرسلة بذلك. وبذلك يمكن لهذه العملية أن تعرف أن المقطع قد وصل إلى وجهته بنجاح، وهو ما لم يكن ممكنًا مع بروتوكول UDP.
- وإذا لم يتلقَ البروتوكول TCP، الذي أرسل مقطعًا، إشعارًا بالاستلام بعد مرور فترة معينة، فإنه يعيد إرسال المقطع المعني، مما يضمن جودة خدمة توصيل المعلومات.
- الدائرة الافتراضية المنشأة بين العمليتين المتواصلتين هي full-duplex: وهذا يعني أن المعلومات يمكن أن تنتقل في كلا الاتجاهين. وبالتالي، يمكن للعملية الوجهة إرسال إقرارات الاستلام حتى في الوقت الذي تواصل فيه العملية المصدر إرسال المعلومات. وهذا يسمح، على سبيل المثال، لبروتوكول المصدر 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، في حين تم تسمية PC و486DX50 باسم liny. كيف يمكن الإشارة إلى هذه الأجهزة من الخارج؟ من خلال تحديد التسلسل الهرمي للمجالات التي تنتمي إليها. وبالتالي، سيكون الاسم الكامل لجهاز Lagaffe هو:
Lagaffe.univ-Angers.fr
داخل المجالات، يمكن استخدام أسماء نسبية. وبالتالي، داخل المجال fr وخارج المجال univ-Angers، يمكن الإشارة إلى الجهاز Lagaffe بـ
Lagaffe.univ-Angers
وأخيرًا، داخل المجال univ-Angers، يمكن الإشارة إليها ببساطة بـ
Lagaffe
وبالتالي، يمكن لأي تطبيق الإشارة إلى جهاز ما باسمه. لكن في النهاية، لا بد من الحصول على عنوان الإنترنت الخاص بهذا الجهاز. كيف يتم ذلك؟ لنفترض أننا نريد، من جهاز A، التواصل مع جهاز B.
- إذا كانت الجهاز B تنتمي إلى نفس المجال الذي تنتمي إليه الجهاز A، فمن المحتمل العثور على عنوانها IP في ملف موجود على الجهاز A.
- وإلا، فستجد الجهاز «أ» في ملف آخر أو في نفس الملف السابق قائمة ببعض خوادم الأسماء مع عناوينها IP. وتقوم خادم الأسماء بمهمة الربط بين اسم الجهاز وعنوانه IP. سترسل الجهاز A طلبًا خاصًا إلى أول خادم أسماء في قائمتها، يُسمى طلب DNS، والذي يتضمن بالتالي اسم الجهاز المطلوب. إذا كان الخادم المستفسر منه يحتوي على هذا الاسم في سجلاته، فسيرسل إلى الجهاز A العنوان المطابق IP. وإلا، فسيجد الخادم بدوره في ملفاته قائمة بخوادم الأسماء التي يمكنه الاستعلام عنها، وسيقوم بذلك. وهكذا سيتم الاستعلام عن عدد معين من خوادم الأسماء، ليس بطريقة عشوائية بل بطريقة تقلل من عدد الطلبات إلى أدنى حد. وإذا تم العثور على الجهاز في النهاية، فستصل الإجابة إلى الجهاز «أ».
XDR: (تمثيل البيانات eXternal)
تم إنشاء هذا البروتوكول بواسطة Sun MicroSystems، وهو يحدد تمثيلًا قياسيًا للبيانات، مستقلًا عن الأجهزة.
RPC: (استدعاء الإجراءات عن بُعد)
تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يعفي المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR
NFS: نظام الملفات الشبكي
تم تعريفه أيضًا بواسطة شركة Sun، ويتيح هذا البروتوكول لجهاز ما «رؤية» نظام الملفات الخاص بجهاز آخر. وهو يعتمد على البروتوكول السابق RPC.
9.1.9. خاتمة
لقد عرضنا في هذه المقدمة بعض الخطوط العريضة لبروتوكولات الإنترنت. وللتعمق في هذا المجال، يمكن قراءة الكتاب الممتاز لدوغلاس كومر:
العنوان | TCP/IP: الهندسة المعمارية، البروتوكولات، التطبيقات. |
المؤلف | دوغلاس COMER |
الناشر | InterEditions |
9.2. إدارة عناوين الشبكة
يتم تعريف أي جهاز على شبكة الإنترنت بشكل فريد بواسطة عنوان IP (بروتوكول الإنترنت) بالصيغة I1.I2.I3.I4 حيث In هو رقم يتراوح بين 1 و 254. ويمكن أيضًا تعريفه بواسطة اسم فريد أيضًا. هذا الاسم ليس إلزاميًا، حيث تستخدم التطبيقات في النهاية عناوين IP للأجهزة. وتوجد هذه الأسماء لتسهيل الأمر على المستخدمين. وبالتالي، يكون من الأسهل، باستخدام متصفح، طلب الوصول إلى http://www.ibm.com بدلاً من URL http://129.42.17.99، على الرغم من أن الطريقتين ممكنتان. يتم الربط بين العنوانين IP و<--> nomMachine بواسطة خدمة موزعة على الإنترنت تُسمى DNS (نظام أسماء النطاقات). توفر منصة .NET فئة Dns لإدارة عناوين الإنترنت:

معظم أساليب الفئة ثابتة. لنلقِ نظرة على تلك التي تهمنا:
تُرجع عنوان IPHostEntry من عنوان IP بالصيغة "I1.I2.I3.I4". تُطلق استثناءً إذا تعذر العثور على الآلة address. | |
تُرجع عنوان IPHostEntry استنادًا إلى اسم الجهاز. تُطلق استثناءً إذا تعذر العثور على الجهاز name. | |
تُرجع اسم الجهاز الذي يعمل عليه البرنامج الذي ينفذ هذه التعليمات |
تتخذ عناوين الشبكة من النوع IPHostEntry الشكل التالي:
![]() |
الخصائص التي تهمنا:
قائمة بعناوين IP الخاصة بجهاز ما. إذا كان العنوان IP يشير إلى جهاز مادي واحد فقط، فهل يمكن أن يكون للجهاز المادي عدة عناوين من نوع IP؟ سيكون هذا هو الحال إذا كان الجهاز مزودًا بعدة بطاقات شبكة تربطه بشبكات مختلفة. | |
قائمة الأسماء المستعارة لجهاز ما، والتي يمكن الإشارة إليها باسم رئيسي وأسماء مستعارة | |
اسم الجهاز إن كان له اسم |
من الفئة IPAddress، سنستخلص المُنشئ والخصائص والطرق التالية:

يمكن تحويل كائن [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
البرنامج كالتالي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
' وحدة الاختبار
Public Module adresses
Sub Main()
' يعرض اسم الجهاز المحلي
' ثم يقدم معلومات تفاعلية عن أجهزة الشبكة
' المحددة باسم أو عنوان IP
' الجهاز المحلي
Dim localHost As String = Dns.GetHostName()
Console.Out.WriteLine(("Machine Locale=" + localHost))
' أسئلة وأجوبة تفاعلية
Dim machine As String
Dim adresseMachine As IPHostEntry
While True
' إدخال اسم الجهاز المطلوب
Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
machine = Console.In.ReadLine().Trim().ToLower()
' انتهى؟
If machine = "fin" Then
Exit While
End If
' عنوان I1.I2.I3.I4 أم اسم الجهاز؟
Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
' معالجة الاستثناء
Try
If isIPV4 Then
adresseMachine = Dns.GetHostByAddress(machine)
Else
adresseMachine = Dns.GetHostByName(machine)
End If
' الاسم
Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
' العناوين IP
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()
' الأسماء المستعارة
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
' الجهاز غير موجود
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، عبر الهاتف: حيث يتم نقل حوارهما عبر الهاتف. يتم ترميز الكلام في شكل إشارات بواسطة الهاتف «أ»، ثم يُنقل عبر خطوط الهاتف، ليصل إلى الهاتف «ب» حيث يتم فك ترميزه. عندئذٍ يسمع الشخص «ب» الكلمات. وهنا يأتي دور مفهوم بروتوكول الحوار: إذا كان «أ» يتحدث الفرنسية و«ب» لا يفهم هذه اللغة، فلن يتمكن «أ» و«ب» من إجراء حوار مفيد.
لذلك، يجب أن يتفق التطبيقان المتواصلان على نوع الحوار الذي سيتبنيانه. على سبيل المثال، الحوار مع خدمة ftp يختلف عن الحوار مع خدمة pop: فهذان الخدمتان لا تقبلان نفس الأوامر. ولديهما بروتوكول حوار مختلف.
9.3.2. خصائص بروتوكول TCP
لن ندرس هنا سوى الاتصالات الشبكية التي تستخدم بروتوكول النقل TCP. ولنتذكر هنا خصائص هذا البروتوكول:
- تقوم العملية التي ترغب في الإرسال أولاً بإنشاء اتصال مع العملية المستقبلة للمعلومات التي سترسلها. يتم هذا الاتصال بين منفذ في الجهاز المرسل ومنفذ في الجهاز المستقبل. يتم إنشاء مسار افتراضي بين المنفذين، ويكون هذا المسار مخصصاً حصرياً للعمليتين اللتين قامتا بإجراء الاتصال.
- تتبع جميع الحزم التي ترسلها العملية المصدرية هذا المسار الافتراضي وتصل بالترتيب الذي أُرسلت به
- تتميز المعلومات المرسلة بطابعها المستمر. حيث تقوم عملية الإرسال بإرسال المعلومات وفقًا لوتيرتها الخاصة. ولا يتم إرسال هذه المعلومات بالضرورة على الفور: فبروتوكول TCP ينتظر حتى يتوفر لديه ما يكفي منها لإرسالها. ويتم تخزينها في بنية تُسمى المقطع TCP. وبمجرد امتلاء هذا المقطع، سيتم إرساله إلى الطبقة IP حيث سيتم تغليفه في حزمة IP.
- يتم ترقيم كل مقطع يتم إرساله عبر بروتوكول TCP. ويتحقق بروتوكول TCP المستلم من استلام المقاطع بالترتيب الصحيح. ولكل مقطع يتم استلامه بشكل صحيح، يرسل إشعارًا بالاستلام إلى المرسل.
- وعندما يتلقى المرسل هذا الإقرار، يقوم بإبلاغ العملية المرسلة بذلك. وبذلك يمكن لهذه العملية أن تعرف أن المقطع قد وصل إلى وجهته بنجاح.
- وإذا لم يتلقَ البروتوكول TCP، الذي أرسل مقطعًا، إشعارًا بالاستلام بعد مرور فترة معينة، فإنه يعيد إرسال المقطع المعني، مما يضمن جودة خدمة توصيل المعلومات.
- الدائرة الافتراضية المنشأة بين العمليتين المتواصلتين هي full-duplex: وهذا يعني أن المعلومات يمكن أن تنتقل في كلا الاتجاهين. وبالتالي، يمكن للعملية الوجهة إرسال إشعارات الاستلام حتى في الوقت الذي تواصل فيه العملية المصدر إرسال المعلومات. وهذا يسمح، على سبيل المثال، لبروتوكول المصدر TCP بإرسال عدة مقاطع دون انتظار إشعار الاستلام. وإذا أدرك بعد فترة معينة أنه لم يتلقَ إشعار الاستلام لمقطع معين رقم n، فسيستأنف إرسال المقاطع من تلك النقطة.
9.3.3. العلاقة بين العميل والخادم
غالبًا ما تكون الاتصالات عبر الإنترنت غير متماثلة: تبدأ الآلة «أ» اتصالاً لطلب خدمة من الآلة «ب»، حيث تحدد أنها تريد فتح اتصال مع الخدمة SB1 التابعة للآلة «ب». وتقوم الآلة «ب» بقبول الطلب أو رفضه. وإذا وافقت، يمكن للجهاز A إرسال طلباته إلى الخدمة SB1. ويجب أن تتوافق هذه الطلبات مع بروتوكول الحوار الذي تفهمه الخدمة SB1. وبذلك ينشأ حوار من نوع «الطلب والاستجابة» بين الجهاز «أ» الذي يُسمى «جهاز العميل» والجهاز «ب» الذي يُسمى «جهاز الخادم». وسيقوم أحد الطرفين بإنهاء الاتصال.
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. وهي معرَّفة على النحو التالي:

المنشئات والأساليب والخصائص التي تهمنا هي التالية:
يُنشئ اتصال TCP مع الخادم الذي يعمل على المنفذ المحدد (port) للجهاز المحدد (hostname). على سبيل المثال، new TcpClient("istia.univ-angers.fr",80) للاتصال بالمنفذ 80 للجهاز istia.univ-angers.fr | |
يغلق الاتصال بخادم TCP | |
يحصل على تدفق NetworkStream للقراءة والكتابة إلى الخادم. وهذا التدفق هو الذي يسمح بالتبادل بين العميل والخادم. |
9.3.7. الفئة NetworkStream
تمثل الفئة NetworkStream تدفق الشبكة بين العميل والخادم. يتم تعريف الفئة على النحو التالي:

الفئة 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
الأمر
تعني أن تدفق الكتابة من client1 لن يمر عبر مخزن مؤقت بل سيتجه مباشرة إلى الشبكة. هذه النقطة مهمة. بشكل عام، عندما يرسل client1 سطرًا نصيًا إلى شريكه، فإنه يتوقع ردًا عليه. ولن تأتي هذه الإجابة أبدًا إذا كان السطر قد تم تخزينه مؤقتًا في الواقع على الجهاز M1 ولم يتم إرساله مطلقًا. لإرسال سطر نصي إلى الجهاز M2، نكتب:
لقراءة الرد الوارد من M2، نكتب:
9.3.8. البنية الأساسية لعميل الإنترنت
لدينا الآن العناصر اللازمة لكتابة البنية الأساسية لعميل الإنترنت:
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' يتم الاتصال بالخدمة العاملة على المنفذ P للجهاز M
client = New TcpClient(nomServeur, port)
' يتم إنشاء تدفقات الإدخال والإخراج للعميل TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' حلقة الطلب - الاستجابة
While True
' يتم إعداد الطلب
demande = ...
' يتم إرسال الطلب إلى الخادم
OUT.WriteLine(demande)
' قراءة الرد من الخادم
réponse = [IN].ReadLine()
' معالجة الرد
...
End While
' انتهى الأمر
client.Close()
Catch ex As Exception
' يتم التعامل مع الاستثناء
...
End Try
9.3.9. الفئة TcpListener
الفئة TcpListener هي الفئة المناسبة لتمثيل خدمة TCP. وهي معرَّفة على النحو التالي:

المنشئات والأساليب والخصائص التي تهمنا هي التالية:
تُنشئ خدمة TCP التي ستنتظر (listen) طلبات العملاء على منفذ يتم تمريره كمعلمة (port) يُسمى منفذ الاستماع للجهاز المحلي الذي عنوانه IP localadr. | |
يقبل طلب العميل. ويعيد كنتيجة كائن TcpClient مرتبطًا بمنفذ آخر، يُسمى منفذ الخدمة. | |
يبدأ في الاستماع إلى طلبات العملاء | |
يتوقف عن الاستماع إلى طلبات العملاء |
9.3.10. البنية الأساسية لخادم الإنترنت
من ما سبق، يمكن استنتاج البنية الأساسية لخادم:
' يتم إنشاء خدمة الاستماع
Dim ecoute As TcpListener = Nothing
Dim port As Integer = ...
Try
' يتم إنشاء الخدمة
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' يتم تشغيلها
ecoute.Start()
' حلقة الخدمة
Dim liaisonClient As TcpClient = Nothing
While not fini
' في انتظار عميل
liaisonClient = ecoute.AcceptTcpClient()
' يتم توفير الخدمة بواسطة مهمة أخرى
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' الإبلاغ عن الخطأ
....
End Try
' نهاية الخدمة
ecoute.Stop()
الفئة Service هي thread التي قد تبدو كما يلي:
Public Class Service
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' المنشئ
Public Sub New(ByVal liaisonClient As TcpClient, ...)
Me.liaisonClient = liaisonClient
...
End Sub
' طريقة run
Public Sub Run()
' تسليم الخدمة إلى العميل
Try
' تدفق الإدخال
[IN] = New StreamReader(liaisonClient.GetStream())
' تدفق المخرجات
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' حلقة قراءة الطلب/كتابة الرد
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' معالجة الطلب
...
' إرسال الرد
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' الطلب التالي
demande = [IN].ReadLine
End While
' نهاية الاتصال
liaisonClient.Close()
Catch e As Exception
...
End Try
' نهاية الخدمة
End Sub
9.4. أمثلة
9.4.1. خادم الصدى
نقترح كتابة خادم إيكو يتم تشغيله من نافذة DOS باستخدام الأمر:
يعمل الخادم على المنفذ الذي تم تمريره كمعلمة. ويكتفي بإعادة إرسال الطلب الذي أرسله العميل إليه. البرنامج هو كما يلي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' استدعاء: serveurEcho المنفذ
' خادم الصدى
' يعيد إلى العميل السطر الذي أرسله إليه
Public Class serveurEcho
Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
' البرنامج الرئيسي
Public Shared Sub Main(ByVal args() As String)
' هل هناك وسيطة
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' يجب أن تكون هذه الحجة عددًا صحيحًا أكبر من 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
' يتم إنشاء خدمة الاستماع
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' يتم إنشاء الخدمة
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' يتم تشغيلها
ecoute.Start()
' المتابعة
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' حلقة الخدمة
Dim liaisonClient As TcpClient = Nothing
While True
' حلقة لا نهائية - سيتم إيقافها بواسطة Ctrl-C
' في انتظار عميل
liaisonClient = ecoute.AcceptTcpClient()
' يتم توفير الخدمة بواسطة مهمة أخرى
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' نعود إلى الاستجابة للطلبات
Catch ex As Exception
' الإبلاغ عن الخطأ
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
' نهاية الخدمة
ecoute.Stop()
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' إيقاف مع خطأ
Environment.Exit(exitCode)
End Sub
End Class
' -------------------------------------------------------
' تقديم الخدمة لعميل خادم الصدى
Public Class traiteClientEcho
Private liaisonClient As TcpClient ' liaison avec le client
Private numClient As Integer ' n° de client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' المنشئ
Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
Me.liaisonClient = liaisonClient
Me.numClient = numClient
End Sub
' طريقة التشغيل
Public Sub Run()
' يقدم الخدمة للعميل
Console.Out.WriteLine(("Début de service au client " & numClient))
Try
' تدفق الإدخال
[IN] = New StreamReader(liaisonClient.GetStream())
' تدفق الإخراج
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' حلقة قراءة الطلب/كتابة الاستجابة
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' المتابعة
Console.Out.WriteLine(("Client " & numClient & " : " & demande))
' تتوقف الخدمة عندما يرسل العميل علامة نهاية الملف
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' تتوقف الخدمة عندما يرسل العميل كلمة "fin"
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' الطلب التالي
demande = [IN].ReadLine
End While
' نهاية الاتصال
liaisonClient.Close()
Catch e As Exception
erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
End Try
' نهاية الخدمة
Console.Out.WriteLine(("Fin de service au client " & numClient))
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' إيقاف مع خطأ
Environment.Exit(exitCode)
End Sub
End Class
تتوافق بنية الخادم مع البنية العامة لخوادم TCP.
9.4.2. عميل لخادم الصدى
سنقوم الآن بكتابة عميل للخادم السابق. سيتم استدعاؤه على النحو التالي:
يتصل الجهاز nomServeur عبر المنفذ port ثم يرسل إلى الخادم أسطر نصية يعيدها الخادم إليه كصدى.
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class clientEcho
' الاتصال بخادم الصدى
' يتم عندئذٍ استلام أي سطر يتم كتابته على لوحة المفاتيح كصدى
Public Shared Sub Main(ByVal args() As String)
' الصيغة
Const syntaxe As String = "pg machine port"
' عدد الوسيطات
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' يتم تدوين اسم الخادم
Dim nomServeur As String = args(0)
' يجب أن يكون المنفذ عددًا صحيحًا أكبر من 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 ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' يتم الاتصال بالخدمة التي تعمل على المنفذ P للجهاز M
client = New TcpClient(nomServeur, port)
' يتم إنشاء تدفقات الإدخال والإخراج للعميل TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' حلقة الطلب - الاستجابة
While True
' يأتي الطلب من لوحة المفاتيح
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' يتم إرسالها إلى الخادم
OUT.WriteLine(demande)
' يتم قراءة الرد من الخادم
réponse = [IN].ReadLine()
' معالجة الرد
Console.Out.WriteLine(("Réponse : " + réponse))
' انتهى؟
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
End While
' انتهى الأمر
client.Close()
Catch ex As Exception
' معالجة الاستثناء
erreur(ex.Message, 3)
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' إيقاف مع خطأ
Environment.Exit(exitCode)
End Sub
End Class
تتوافق بنية هذا العميل مع البنية العامة لعملاء tcp.Voici، وقد تم الحصول على النتائج التالية في التكوين التالي:
- يتم تشغيل الخادم على المنفذ 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 خادم منفذ
سيتصل هذا العميل TCP بالمنفذ port الخاص بالخادم serveur. وبمجرد القيام بذلك، سيقوم بإنشاء خيطين:
- خيط مسؤول عن قراءة الأوامر المكتوبة على لوحة المفاتيح وإرسالها إلى الخادم
- خيط آخر مسؤول عن قراءة ردود الخادم وعرضها على الشاشة
لماذا نحتاج إلى خيطين في حين أن الحاجة إلى ذلك لم تكن مطلوبة في التطبيق السابق؟ في التطبيق السابق، كان بروتوكول الحوار معروفًا: كان العميل يرسل سطرًا واحدًا ويجيب الخادم بسطر واحد. لكل خدمة بروتوكولها الخاص، كما توجد الحالات التالية:
- يجب على العميل إرسال عدة أسطر من النص قبل الحصول على رد
- قد يتضمن رد الخادم عدة أسطر من النص
لذلك، فإن الحلقة التي تتمثل في إرسال سطر واحد إلى الخادم - واستلام سطر واحد مرسَل من الخادم - لا تكون مناسبة دائمًا. لذا سنقوم بإنشاء حلقتين منفصلتين:
- حلقة لقراءة الأوامر المكتوبة على لوحة المفاتيح لإرسالها إلى الخادم. سيشير المستخدم إلى نهاية الأوامر باستخدام الكلمة الرئيسية fin.
- حلقة لاستقبال وعرض ردود الخادم. وستكون هذه الحلقة حلقة لا نهائية لا تتوقف إلا بإغلاق تدفق الشبكة من قِبل الخادم أو من قِبل المستخدم عبر لوحة المفاتيح عند كتابة الأمر fin.
ولفصل هاتين الحلقتين عن بعضهما، نحتاج إلى خيطين مستقلين. لنعرض مثالاً على التنفيذ حيث يتصل عميل TCP العام لدينا بخدمة SMTP (بروتوكول النقل SendMail). هذه الخدمة مسؤولة عن توجيه البريد الإلكتروني إلى المستلمين. وهي تعمل على المنفذ 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 بإرسال رسالة ترحيب عندما يتصل بها أحد العملاء:
- تحتوي بعض الخدمات على أمر help يقدم إرشادات حول الأوامر التي يمكن استخدامها مع الخدمة. لكن هذا ليس هو الحال هنا. الأوامر SMTP المستخدمة في المثال هي التالية:
- mail from: expéditeur، للإشارة إلى العنوان الإلكتروني لمرسل الرسالة
- rcpt to: destinataire، للإشارة إلى العنوان الإلكتروني لمستلم الرسالة. وفي حالة وجود عدة مستلمين، يتم إعادة إرسال الأمر rcpt to: عدة مرات حسب الحاجة لكل مستلم على حدة.
- بيانات تُعلم الخادم SMTP بأننا سنرسل الرسالة. وكما هو موضح في رد الخادم، فإن هذه البيانات عبارة عن سلسلة من الأسطر تنتهي بسطر يحتوي على حرف النقطة فقط. قد تحتوي الرسالة على رؤوس مفصولة عن نص الرسالة بسطر فارغ. في مثالنا هذا، أضفنا موضوعًا باستخدام الكلمة الرئيسية Subject:
- بمجرد إرسال الرسالة، يمكن إعلام الخادم بأننا انتهينا باستخدام الأمر quit. عندئذٍ يقوم الخادم بإغلاق الاتصال réseau.Le، ويمكن لسلسلة القراءة (thread) اكتشاف هذا الحدث والتوقف.
- ثم يكتب المستخدم «fin» على لوحة المفاتيح لإيقاف مؤشر القراءة الخاص بالأوامر المكتوبة على لوحة المفاتيح أيضًا.
إذا تحققنا من البريد الوارد، فسنجد ما يلي (Outlook):

يُلاحظ أن الخدمة SMTP لا يمكنها تحديد ما إذا كان المرسل صالحًا أم لا. ولذلك لا يمكن أبدًا الوثوق بحقل from في أي رسالة. في هذه الحالة، لم يكن المرسل 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]
الأوامر الرئيسية هي التالية:
- user login، حيث يتم إدخال اسم المستخدم الخاص بك على الجهاز الذي يحتوي على بريدك الإلكتروني
- pass password، حيث يتم إدخال كلمة المرور المرتبطة باسم المستخدم السابق
- list، للحصول على قائمة الرسائل في شكل رقم وحجم بالبايت
- retr i، لقراءة الرسالة رقم i
- quit، لإنهاء الحوار.
لنكتشف الآن بروتوكول الحوار بين العميل وخادم الويب الذي يعمل عادةً على المنفذ 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]
يرسل عميل الويب أوامره إلى الخادم وفقًا للنموذج التالي:
لا يستجيب خادم الويب إلا بعد تلقي السطر الفارغ. في المثال، لم نستخدم سوى أمر واحد:
الذي يطلب من الخادم URL /index.html ويشير إلى أنه يعمل باستخدام بروتوكول HTTP الإصدار 1.0. أحدث إصدار من هذا البروتوكول هو 1.1. يوضح المثال أن الخادم استجاب بإرسال محتوى الملف index.html ثم أغلق الاتصال، حيث نرى أن مؤشر ترابط قراءة الردود قد انتهى. قبل إرسال محتوى الملف index.html، أرسل خادم الويب سلسلة من الرؤوس تنتهي بسطر فارغ:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
السطر <html> هو السطر الأول من الملف /index.html. ما سبق يُسمى رؤوس HTTP (بروتوكول النقل HyperText). لن ندخل هنا في تفاصيل هذه الرؤوس، لكن تجدر الإشارة إلى أن عميلنا العام يتيح الوصول إليها، وهو ما قد يكون مفيدًا لفهمها. على سبيل المثال، السطر الأول:
يشير إلى أن خادم الويب الذي تم الاتصال به يدعم بروتوكول HTTP/1.1 وأنه عثر بالفعل على الملف المطلوب (200 OK)، حيث يمثل الرقم 200 رمز استجابة HTTP. الأسطر
تُعلم العميل بأنه سيتلقى 11251 بايت تمثل النص HTML (لغة ترميز HyperText) وأنه عند انتهاء الإرسال، سيتم إغلاق الاتصال. إذن، لدينا هنا عميل TCP عملي للغاية. في الواقع، هذا العميل موجود بالفعل على الأجهزة حيث يُسمى telnet، لكن كان من المثير للاهتمام أن نكتبه بأنفسنا. برنامج عميل TCP العام هو كما يلي:
' مساحات الأسماء
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' الفئة
Public Class clientTcpGénérique
' تتلقى كمعلمة خصائص خدمة ما بالشكل
' الخادم والمنفذ
' يتصل بالخدمة
' يُنشئ مؤشر ترابط لقراءة الأوامر المكتوبة على لوحة المفاتيح
' سيتم إرسال هذه الأوامر إلى الخادم
' إنشاء مؤشر ترابط لقراءة الردود الواردة من الخادم
' سيتم عرض هذه الردود على الشاشة
' وينتهي كل ذلك بإدخال الأمر «fin» عبر لوحة المفاتيح
Public Shared Sub Main(ByVal args() As String)
' الصيغة
Const syntaxe As String = "pg serveur port"
' عدد الوسيطات
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' يُذكر اسم الخادم
Dim serveur As String = args(0)
' يجب أن يكون المنفذ عددًا صحيحًا أكبر من 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
' قد تحدث بعض المشاكل
Try
' يتم الاتصال بالخدمة
client = New TcpClient(serveur, port)
Catch ex As Exception
' خطأ
Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
' نهاية
Return
End Try
' يتم إنشاء خيوط القراءة/الكتابة
Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
' يتم تشغيل الخيطين
thSend.Start()
thReceive.Start()
' نهاية الخيط الرئيسي
Return
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' التوقف مع حدوث خطأ
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' الفئة المسؤولة عن قراءة الأوامر المكتوبة على لوحة المفاتيح
' وإرسالها إلى خادم عبر عميل TCP تم تمريره إلى المنشئ
Private client As TcpClient ' le client tcp
' المنشئ
Public Sub New(ByVal client As TcpClient)
' يتم تسجيل عميل TCP
Me.client = client
End Sub
' طريقة Run الخاصة بالخيط
Public Sub Run()
' البيانات المحلية
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim commande As String = Nothing ' commande lue au clavier
' إدارة الأخطاء
Try
' إنشاء تدفق الكتابة عبر الشبكة
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' حلقة إدخال وإرسال الأوامر
Console.Out.WriteLine("Commandes : ")
While True
' قراءة الأمر المكتوب على لوحة المفاتيح
commande = Console.In.ReadLine().Trim()
' انتهى؟
If commande.ToLower() = "fin" Then
Exit While
End If
' إرسال الأمر إلى الخادم
OUT.WriteLine(commande)
End While
Catch ex As Exception
' خطأ
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' النهاية - إغلاق التدفقات
Try
OUT.Close()
client.Close()
Catch
End Try
' الإبلاغ عن انتهاء مؤشر الترابط
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' الفئة المسؤولة عن قراءة أسطر النص الموجهة إلى
' عميل TCP تم تمريره إلى المنشئ
Private client As TcpClient ' le client tcp
' الشركة المصنعة
Public Sub New(ByVal client As TcpClient)
' يُلاحظ أن العميل هو TCP
Me.client = client
End Sub
'المُصنِّع
' طريقة تشغيل الخيط
Public Sub Run()
' البيانات المحلية
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' إدارة الأخطاء
Try
' إنشاء تدفق قراءة الشبكة
[IN] = New StreamReader(client.GetStream())
' حلقة قراءة أسطر النص من التدفق IN
While True
' قراءة تدفق الشبكة
réponse = [IN].ReadLine()
' هل التدفق مغلق؟
If réponse Is Nothing Then
Exit While
End If
' العرض
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' خطأ
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' النهاية - يتم إغلاق التدفقات
Try
[IN].Close()
client.Close()
Catch
End Try
' الإبلاغ عن نهاية سلسلة الرسائل
Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
End Sub
End Class
9.4.4. خادم TCP عام
ننتقل الآن إلى خادم
- يعرض على الشاشة الأوامر المرسلة من قبل عملائه
- ويرسل إليهم كردًّا الأسطر النصية التي يكتبها المستخدم على لوحة المفاتيح. وبالتالي، فإن هذا الأخير هو الذي يقوم بدور الخادم.
يتم تشغيل البرنامج بواسطة: srvgen portEcoute، حيث يمثل portEcoute المنفذ الذي يجب على العملاء الاتصال به. وسيتم توفير الخدمة للعميل بواسطة خيطين:
- خيط مخصص حصريًّا لقراءة أسطر النص المرسلة من العميل
- خيط يكرس جهوده حصريًا لقراءة الردود التي يكتبها المستخدم على لوحة المفاتيح. ويقوم هذا الخيط بالإشارة، عبر الأمر 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:
لنستخدم الآن متصفحًا ونطلب الصفحة http://localhost:88/exemple.html. سيتصل المتصفح عندئذٍ بالمنفذ 88 الخاص بالجهاز localhost ثم يطلب الصفحة /exemple.html:

لنلقِ نظرة الآن على نافذة الخادم الخاص بنا:
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. الأمر fin ينهي اتصال الخادم بالعميل. وقد اقتصرنا في ردنا على الرؤوس التالية لـ HTTP:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
نحن لا نحدد حجم الملف الذي سنرسله (Content-Length)، بل نكتفي بالقول إننا سنغلق الاتصال (Connection: close) بعد إرساله. وهذا يكفي للمتصفح. وعندما يرى المتصفح أن الاتصال قد أُغلق، سيعلم أن استجابة الخادم قد اكتملت، وسيقوم بعرض الصفحة HTML التي أُرسلت إليه. وهذه الصفحة هي كما يلي:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
ثم يقوم المستخدم بإغلاق الاتصال بالعميل عن طريق كتابة الأمر fin. عندئذٍ يدرك المتصفح أن استجابة الخادم قد اكتملت ويمكنه عرضها:

وإذا أدخلنا الأمر Affichage/Source أعلاه لمعرفة ما تلقّاه المتصفح، فسنحصل على:

أي بالضبط ما تم إرساله من الخادم العام. رمز الخادم العام TCP هو التالي:
' مساحات الأسماء
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class serveurTcpGénérique
' البرنامج الرئيسي
Public Shared Sub Main(ByVal args() As String)
' يستقبل المنفذ الذي يستمع لطلبات العملاء
' يُنشئ مؤشر ترابط لقراءة طلبات العميل
' سيتم عرض هذه الطلبات على الشاشة
' إنشاء مؤشر ترابط لقراءة الأوامر المكتوبة على لوحة المفاتيح
' سيتم إرسال هذه الأوامر كرد إلى العميل
' ينتهي كل ذلك بإدخال الأمر «fin» عبر لوحة المفاتيح
Const syntaxe As String = "Syntaxe : pg port"
' هل هناك وسيطة؟
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' يجب أن تكون هذه الحجة عددًا صحيحًا أكبر من 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
' يتم إنشاء خدمة الاستماع
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' يتم إنشاء الخدمة
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' يتم تشغيلها
ecoute.Start()
' المتابعة
Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
' حلقة خدمة العملاء
Dim client As TcpClient = Nothing
While True ' boucle infinie - sera arrêtée par Ctrl-C
' في انتظار عميل
client = ecoute.AcceptTcpClient()
' يتم توفير الخدمة عبر خيوط منفصلة
nbClients += 1
' خيط قراءة طلبات العملاء
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' خيط قراءة الردود التي يكتبها المستخدم على لوحة المفاتيح
Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
' يتم بدء تشغيل الخيطين
thSend.Start()
thReceive.Start()
End While
' العودة إلى الاستماع لطلبات العملاء
Catch ex As Exception
' الإبلاغ عن الخطأ
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' التوقف مع حدوث خطأ
Environment.Exit(exitCode)
End Sub
End Class
Public Class serveurSend
' فئة مكلفة بقراءة الإجابات المكتوبة على لوحة المفاتيح
' وإرسالها إلى عميل عبر عميل TCP تم تمريره إلى المنشئ
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' منشئ
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' يتم تسجيل عميل TCP
Me.client = client
' ورقمه
Me.numClient = numClient
End Sub
' طريقة Run الخاصة بالخيط
Public Sub Run()
' البيانات المحلية
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim réponse As String = Nothing ' réponse lue au clavier
' المتابعة
Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
' إدارة الأخطاء
Try
' إنشاء تدفق الكتابة عبر الشبكة
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' حلقة إدخال الأوامر وإرسالها
While True
' تحديد هوية العميل
Console.Out.Write((numClient & " : "))
' قراءة الرد المكتوب على لوحة المفاتيح
réponse = Console.In.ReadLine().Trim()
' هل انتهيت؟
If réponse.ToLower() = "fin" Then
Exit While
End If
' إرسال الرد إلى الخادم
OUT.WriteLine(réponse)
End While
' الرد التالي
Catch ex As Exception
' خطأ
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' النهاية - إغلاق التدفقات
Try
OUT.Close()
client.Close()
Catch
End Try
' الإبلاغ عن انتهاء مؤشر الترابط
Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
End Sub
End Class
Public Class serveurReceive
' الفئة المسؤولة عن قراءة أسطر النص المرسلة إلى الخادم
' عبر عميل TCP تم تمريره إلى المنشئ
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' منشئ
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' يتم تسجيل عميل TCP
Me.client = client
' ورقمه
Me.numClient = numClient
End Sub
' طريقة Run الخاصة بالخيط
Public Sub Run()
' البيانات المحلية
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' المتابعة
Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
' إدارة الأخطاء
Try
' إنشاء تدفق قراءة الشبكة
[IN] = New StreamReader(client.GetStream())
' حلقة قراءة أسطر النص من التدفق IN
While True
' قراءة تدفق الشبكة
réponse = [IN].ReadLine()
' هل التدفق مغلق؟
If réponse Is Nothing Then
Exit While
End If
' العرض
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' خطأ
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' النهاية - يتم إغلاق التدفقات
Try
[IN].Close()
client.Close()
Catch
End Try
' الإبلاغ عن نهاية مؤشر الترابط
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 بـ HEAD، فلن يرسل لنا الخادم سوى الرؤوس HTTP ولن يرسل لنا الصفحة HTML.
سيتم استدعاء عميل الويب الخاص بنا على النحو التالي: clientweb 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
الإجابة
يعني أن الصفحة المطلوبة قد تغير مكانها (أي من URL). ويتم تحديد العنوان الجديد URL من خلال رأس Location:
إذا استخدمنا 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. البرنامج هو كما يلي:
' مساحات الأسماء
Imports System
Imports System.Net.Sockets
Imports System.IO
Public Class clientWeb1
' طلب URL
' يعرض محتوى ذلك على الشاشة
Public Shared Sub Main(ByVal args() As String)
' الصيغة
Const syntaxe As String = "pg URI GET/HEAD"
' عدد المعلمات
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' يتم تسجيل URI المطلوب
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' التحقق من صحة URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' Rمز URI غير صحيح
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' التحقق من الطلب
If commande <> "GET" And commande <> "HEAD" Then
' الطلب غير صحيح
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' يمكن العمل
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' يتم الاتصال بالخادم
client = New TcpClient(uri.Host, uri.Port)
' يتم إنشاء تدفقات الدخول والخروج للعميل TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' يتم طلب URL - إرسال الرؤوس HTTP
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' يتم قراءة الرد
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' معالجة الرد
Console.Out.WriteLine(réponse)
' يتم قراءة الرد
réponse = [IN].ReadLine()
End While
' انتهى الأمر
client.Close()
Catch e As Exception
' معالجة الاستثناء
erreur(e.Message, 4)
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' التوقف مع وجود خطأ
Environment.Exit(exitCode)
End Sub
End Class
الشيء الجديد الوحيد في هذا البرنامج هو استخدام فئة Uri. يتلقى البرنامج عنوان URL (محدد موقع الموارد الموحد) أو URI (معرف الموارد الموحد) بالصيغة http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... تسمح لنا فئة Uri بتفكيك سلسلة URL إلى عناصرها المختلفة. يتم إنشاء كائن Uri من السلسلة URIstring المستلمة كمعلمة:
' التحقق من صحة URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI غير صحيح
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=serveur، uri.Port=port، uri.Path=cheminPageHTML، uri.Query=param1=val1;param2=val2;...، uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.
9.4.6. متصفح الويب الذي يدير عمليات إعادة التوجيه
لا يدعم عميل الويب السابق أي إعادة توجيه محتملة لـ URL التي طلبها. أما العميل التالي فيدعمها.
- يقرأ السطر الأول من رؤوس HTTP المرسلة من الخادم للتحقق مما إذا كانت تحتوي على السلسلة «302 Object moved» التي تشير إلى إعادة التوجيه
- يقرأ الرؤوس التالية. في حالة وجود إعادة توجيه، يبحث عن السطر «Location: url» الذي يحدد العنوان الجديد URL للصفحة المطلوبة ويسجل هذا العنوان URL.
- يعرض بقية استجابة الخادم. في حالة وجود إعادة توجيه، تتكرر الخطوات من 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>
البرنامج كالتالي:
' مساحات الأسماء
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
' فئة عميل الويب
Public Class clientWeb
' يطلب URL ويعرض محتواه على الشاشة
Public Shared Sub Main(ByVal args() As String)
' الصيغة
Const syntaxe As String = "pg URI GET/HEAD"
' عدد الوسيطات
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' يتم تسجيل URI المطلوب
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' التحقق من صحة URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' رمز URI غير صحيح
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try 'catch
' التحقق من الطلب
If commande <> "GET" And commande <> "HEAD" Then
' الطلب غير صحيح
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' يمكن العمل
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
Dim premièreLigne As String ' 1ère ligne de la réponse
Dim redir As Boolean = False ' indique s'il y a redirection ou non
Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
' تعبير منتظم للعثور على URL لإعادة التوجيه
Dim location As New Regex("^Location: (.+?)$") '
' إدارة الأخطاء
Try
' يمكن أن يكون هناك عدة URL يجب الاستعلام عنها في حالة وجود عمليات إعادة توجيه
While nbRedirs <= nbRedirsMax
' يتم الاتصال بالخادم
client = New TcpClient(uri.Host, uri.Port)
' يتم إنشاء تدفقات الإدخال والإخراج للعميل TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' يتم إرسال الرؤوس HTTP لطلب URL
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' يتم قراءة السطر الأول من الرد
premièreLigne = [IN].ReadLine()
' عرض الشاشة
Console.Out.WriteLine(premièreLigne)
' إعادة توجيه؟
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' هناك إعادة توجيه
redir = True
nbRedirs += 1
End If
' رؤوس HTTP التالية حتى العثور على السطر الفارغ الذي يشير إلى نهاية الرؤوس
Dim locationFound As Boolean = False
réponse = [IN].ReadLine()
While réponse <> ""
' يتم عرض الرد
Console.Out.WriteLine(réponse)
' في حالة وجود إعادة توجيه، يتم البحث عن رأس Location
If redir And Not locationFound Then
' نقارن السطر بالتعبير العلائقي location
Dim résultat As Match = location.Match(réponse)
If résultat.Success Then
' إذا تم العثور عليه، يتم تسجيل URL الخاص بإعادة التوجيه
locationString = résultat.Groups(1).Value
' يتم تسجيل أنه تم العثور على
locationFound = True
End If
End If
' السطر التالي
réponse = [IN].ReadLine()
End While
' السطور التالية من الإجابة
Console.Out.WriteLine(réponse)
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' نقوم بعرض الرد
Console.Out.WriteLine(réponse)
' السطر التالي
réponse = [IN].ReadLine()
End While
' يتم إغلاق الاتصال
client.Close()
' هل انتهينا؟
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' هناك إعادة توجيه يجب إجراؤها - يتم إنشاء عنوان URI الجديد
URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
uri = New Uri(URIstring)
' المتابعة
Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
End While
Catch e As Exception
' معالجة الاستثناء
erreur(e.Message, 4)
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' التوقف مع حدوث خطأ
Environment.Exit(exitCode)
End Sub
End Class
9.4.7. خادم حساب الضرائب
نستأنف التمرين IMPOTS الذي تمت معالجته بالفعل بأشكال مختلفة. دعونا نستعرض النسخة الأخيرة. تم إنشاء فئة «ضريبة». وتتمثل سماتها في ثلاثة جداول من الأرقام:
Public Class impôt
' البيانات اللازمة لحساب الضريبة
' تأتي من مصدر خارجي
Private limites(), coeffR(), coeffN() as double
تحتوي الفئة على منشئين:
- منشئ يتم تمرير إليه المصفوفات الثلاثة اللازمة لحساب الضريبة
// المُنشئ 1
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' يقوم بتهيئة المصفوفات الثلاثة للحدود، coeffR، coeffN انطلاقًا من
' المعلمات التي تم تمريرها إلى المنشئ
- مُنشئ يتم تمرير الاسم إليه DSN من قاعدة بيانات ODBC
' المنشئ 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' يقوم بتهيئة المصفوفات الثلاث للحدود، coeffR، coeffN، انطلاقًا من
' محتوى الجدول Timpots في قاعدة البيانات ODBC DSNimpots
' colLimites، colCoeffR، colCoeffN هي الأعمدة الثلاثة في هذا الجدول
' قد يثير استثناءً
تمت كتابة برنامج اختبار:
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
في هذه الحالة، كان برنامج الاختبار والكائن impôt موجودين على نفس الجهاز. ونقترح وضع برنامج الاختبار والكائن impôt على جهازين مختلفين. سيكون لدينا تطبيق عميل-خادم حيث سيكون الكائن البعيد impôt هو الخادم. تسمى الفئة الجديدة ServeurImpots وهي مشتقة من الفئة impôt:
Public Class ServeurImpots
Inherits impôt
' السمات
Private portEcoute As Integer ' le port d'écoute des demandes clients
Private actif As Boolean ' état du serveur
' منشئ
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)
' يُلاحظ منفذ الاستماع
Me.portEcoute = portEcoute
' غير نشط في الوقت الحالي
actif = False
' ينشئ ويطلق مؤشر ترابط لقراءة الأوامر المكتوبة على لوحة المفاتيح
' سيتم إدارة الخادم بناءً على هذه الأوامر
Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
threadLecture.Start()
End Sub
المعلمة الجديدة الوحيدة في منشئ الكائن هي منفذ الاستماع لطلبات العملاء. أما المعلمات الأخرى، فيتم تمريرها مباشرةً إلى الفئة الأساسية impôt. يتم التحكم في خادم الضرائب عن طريق الأوامر التي يتم إدخالها عبر لوحة المفاتيح. ولذلك، نقوم بإنشاء مؤشر ترابط لقراءة هذه الأوامر. سيكون هناك خياران ممكنان: start لتشغيل الخدمة، وstop لإيقافها نهائيًا. الطريقة admin التي تدير هذه الأوامر هي كما يلي:
Public Sub admin()
' يقرأ أوامر إدارة الخادم التي يتم إدخالها عبر لوحة المفاتيح
' في حلقة لا نهائية
Dim commande As String = Nothing
While True
' تطلب
Console.Out.Write("Serveur d'impôts>")
' قراءة الأمر
commande = Console.In.ReadLine().Trim().ToLower()
' تنفيذ الأمر
If commande = "start" Then
' نشط؟
If actif Then
'خطأ
Console.Out.WriteLine("Le serveur est déjà actif")
Else
' يتم تشغيل خدمة الاستماع
Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
threadEcoute.Start()
End If
Else
If commande = "stop" Then
' إنهاء جميع سلاسل التنفيذ
Environment.Exit(0)
Else
' خطأ
Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
End If
End If
End While
End Sub
إذا كان الأمر الذي تم إدخاله عبر لوحة المفاتيح هو start، يتم تشغيل مؤشر ترابط للاستماع إلى طلبات العملاء. أما إذا كان الأمر الذي تم إدخاله هو stop، يتم إيقاف جميع مؤشرات الترابط. يقوم مؤشر الترابط المستمع بتنفيذ الطريقة ecoute:
Public Sub ecoute()
' مؤشر ترابط للاستماع إلى طلبات العملاء
' يتم إنشاء خدمة الاستماع
Dim ecoute As TcpListener = Nothing
Try
' يتم إنشاء الخدمة
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' تشغيلها
ecoute.Start()
' المتابعة
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
' حلقة الخدمة
Dim liaisonClient As TcpClient = Nothing
While True ' boucle infinie
' في انتظار عميل
liaisonClient = ecoute.AcceptTcpClient()
' يتم توفير الخدمة بواسطة مهمة أخرى
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' العودة إلى الاستماع للطلبات
Catch ex As Exception
' الإبلاغ عن الخطأ
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' إيقاف مع خطأ
Environment.Exit(exitCode)
End Sub
يوجد خادم TCP تقليدي يستمع على المنفذ portEcoute. تتم معالجة طلبات العملاء بواسطة الطريقة Run الخاصة بكائن يتم تمرير معلمتين إليه:
- الكائن TcpClient الذي سيسمح بالوصول إلى العميل
- الكائن impôt this الذي سيتيح الوصول إلى الطريقة this.calculer لحساب الضريبة.
' -------------------------------------------------------
' تقديم الخدمة لعميل خادم الضرائب
Public Class traiteClientImpots
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
Private objImpôt As impôt ' objet Impôt
' المنشئ
Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
Me.liaisonClient = liaisonClient
Me.objImpôt = objImpôt
End Sub
تقوم الطريقة Run بمعالجة طلبات العملاء. ويمكن أن تتخذ هذه الطلبات شكلين:
- حساب المتزوجين (نعم/لا) nbEnfants salaireAnnuel
- الحسابات المالية
يسمح النموذج 1 بحساب الضريبة، بينما يغلق النموذج 2 الاتصال بين العميل والخادم.
' طريقة Run
Public Sub Run()
' يقدم الخدمة للعميل
Try
' تدفق الإدخال
[IN] = New StreamReader(liaisonClient.GetStream())
' تدفق المخرجات
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' إرسال رسالة ترحيب إلى العميل
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' حلقة قراءة الطلب/كتابة الرد
Dim demande As String = Nothing
Dim champs As String() = Nothing ' les éléments de la demande
Dim commande As String = Nothing ' la commande du client : calcul ou fincalculs
demande = [IN].ReadLine()
While Not (demande Is Nothing)
' تفكيك الطلب إلى حقول
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' تم قبول طلبين: الحساب وإنهاء الحسابات
commande = champs(0)
Dim erreur As Boolean = False
If commande <> "calcul" And commande <> "fincalculs" Then
' خطأ من جانب العميل
OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
End If
If commande = "calcul" Then
calculerImpôt(champs)
End If
If commande = "fincalculs" Then
' رسالة وداع للعميل
OUT.WriteLine("Au revoir...")
' تحرير الموارد
Try
OUT.Close()
[IN].Close()
liaisonClient.Close()
Catch
End Try
' النهاية
Return
End If
' طلب جديد
demande = [IN].ReadLine()
End While
Catch e As Exception
erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
End Try
End Sub
يتم حساب الضريبة بواسطة الطريقة calculerImpôt التي تتلقى كمعلمة جدول الحقول الخاص بالطلب المقدم من العميل. يتم التحقق من صحة الطلب، ثم يتم حساب الضريبة وإرسالها إلى العميل عند الاقتضاء.
' حساب الضرائب
Public Sub calculerImpôt(ByVal champs() As String)
' معالجة الطلب: حساب المتزوجين nbEnfants salaireAnnuel
' مقسمة إلى حقول في جدول الحقول
Dim marié As String = Nothing
Dim nbEnfants As Integer = 0
Dim salaireAnnuel As Integer = 0
' صحة المعطيات
Try
' يجب وجود 4 حقول على الأقل
If champs.Length <> 4 Then
Throw New Exception
End If
' متزوج
marié = champs(1)
If marié <> "o" And marié <> "n" Then
Throw New Exception
End If
' أطفال
nbEnfants = Integer.Parse(champs(2))
' الراتب
salaireAnnuel = Integer.Parse(champs(3))
Catch
OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
' انتهى
Exit Sub
End Try
' يمكن حساب الضريبة
Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
' إرسال الرد إلى العميل
OUT.WriteLine(impot.ToString)
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' التوقف مع وجود خطأ
Environment.Exit(exitCode)
End Sub
يتم ترجمة هذه الفئة بواسطة
حيث تحتوي impots.dll على رمز الفئة impôt. قد يكون برنامج الاختبار كما يلي:
' مساحات الأسماء
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
Public Class testServeurImpots
Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
' البرنامج الرئيسي
Public Shared Sub Main(ByVal args() As String)
' يلزم 6 معلمات
If args.Length <> 6 Then
erreur(syntaxe, 1)
End If
' يجب أن يكون المنفذ عددًا صحيحًا >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
' يتم إنشاء خادم الضرائب
Try
Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
Catch ex As Exception
'خطأ
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
End Sub
' عرض الأخطاء
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' عرض الخطأ
System.Console.Error.WriteLine(msg)
' إيقاف مع وجود خطأ
Environment.Exit(exitCode)
End Sub
End Class
نمرر إلى برنامج الاختبار البيانات اللازمة لإنشاء كائن ServeurImpots، ومن ثم يقوم البرنامج بإنشاء هذا الكائن. يتم ترجمة برنامج الاختبار هذا بواسطة:
فيما يلي الاختبار الأول:
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
السطر
يُنشئ كائنًا باسم ServeurImpots لا يستقبل طلبات العملاء بعد. إن الأمر «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 أخرى:
نلاحظ أن العميل قد تلقى رسالة الترحيب من الخادم بنجاح. نرسل أوامر أخرى:
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]
نعود إلى نافذة الخادم لإيقاف تشغيله:















