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.
ينقل الحزم بين عقدتين في الشبكة | |
يسهل ICMP الاتصال بين برنامج بروتوكول IP على جهاز ما وبرنامج بروتوكول IP على جهاز آخر. ولذلك فهو بروتوكول لتبادل الرسائل داخل بروتوكول IP نفسه. | |
يقوم بتعيين عنوان الإنترنت الخاص بجهاز ما إلى عنوانه الفعلي | |
يربط العنوان المادي للجهاز بعنوان الإنترنت الخاص به |
طبقات النقل/الجلسة
تتضمن هذه الطبقة البروتوكولات التالية:
يضمن التسليم الموثوق للمعلومات بين عميلين | |
يضمن توصيل المعلومات بشكل غير موثوق بين عميلين |
طبقات التطبيق/العرض/الجلسة
توجد بروتوكولات متنوعة هنا:
محاكي محطة طرفية يسمح للجهاز A بالاتصال بالجهاز B كمحطة طرفية | |
يتيح نقل الملفات | |
يتيح نقل الملفات | |
يتيح تبادل الرسائل بين مستخدمي الشبكة | |
يحول اسم الجهاز إلى عنوان الإنترنت الخاص به | |
تم إنشاؤه بواسطة Sun Microsystems، ويحدد معيارًا لتمثيل البيانات المستقل عن الجهاز | |
تم تعريفه أيضًا بواسطة Sun، وهو بروتوكول اتصال بين التطبيقات البعيدة، مستقل عن طبقة النقل. هذا البروتوكول مهم: فهو يحرر المبرمج من الحاجة إلى معرفة تفاصيل طبقة النقل ويجعل التطبيقات قابلة للنقل. يعتمد هذا البروتوكول على بروتوكول XDR | |
تم تعريفه أيضًا بواسطة 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 لإدارة عناوين الإنترنت:

معظم أساليب الفئة ثابتة. دعونا نلقي نظرة على تلك التي تهمنا:
تُرجع IPHostEntry من عنوان IP بالصيغة "I1.I2.I3.I4". تُطلق استثناءً إذا تعذر العثور على عنوان الجهاز. | |
تُرجع IPHostEntry من اسم جهاز. تُطلق استثناءً إذا تعذر العثور على اسم الجهاز. | |
تُرجع اسم الجهاز الذي يعمل عليه البرنامج الذي ينفذ هذه التعليمات |
تتخذ عناوين شبكة 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
البرنامج كالتالي:
' 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. وهي معرّفة على النحو التالي:

فيما يلي المنشئات والأساليب والخصائص التي تهمنا:
ينشئ اتصال 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 ' 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. وهي معرّفة على النحو التالي:

فيما يلي المنشئات والطرق والخصائص التي تهمنا:
تنشئ خدمة TCP تستمع لطلبات العملاء على منفذ يتم تمريره كمعلمة (port)، والمعروف باسم منفذ الاستماع للجهاز المحلي الذي يحمل عنوان IP localaddr. | |
يقبل طلب العميل. يُرجع كائن TcpClient مرتبط بمنفذ آخر، يُسمى منفذ الخدمة. | |
يبدأ في الاستماع لطلبات العميل | |
توقف الاستماع لطلبات العميل |
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 باستخدام الأمر:
يعمل الخادم على المنفذ الذي تم تمريره كمعلمة. وهو ببساطة يعيد إلى العميل الطلب الذي أرسله العميل إليه. البرنامج كالتالي:
' 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. عميل لخادم الصدى
سنقوم الآن بكتابة عميل للخادم السابق. وسيتم استدعاؤه على النحو التالي:
يتصل هذا البرنامج بالجهاز 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. بمجرد الاتصال، سيقوم بإنشاء مؤشرين:
- خيط مسؤول عن قراءة الأوامر المكتوبة على لوحة المفاتيح وإرسالها إلى الخادم
- خيط مسؤول عن قراءة استجابات الخادم وعرضها على الشاشة
لماذا خيطان بينما لم يكن ذلك ضروريًا في التطبيق السابق؟ في ذلك التطبيق، كان بروتوكول الاتصال ثابتًا: أرسل العميل سطرًا واحدًا، ورد الخادم بسطر واحد. لكل خدمة بروتوكولها الخاص، ونواجه أيضًا الحالات التالية:
- يجب على العميل إرسال عدة أسطر من النص قبل تلقي الرد
- قد يحتوي رد الخادم على عدة أسطر من النص
لذلك، فإن الحلقة التي ترسل سطرًا واحدًا إلى الخادم وتستقبل سطرًا واحدًا من الخادم ليست مناسبة دائمًا. لذلك سننشئ حلقتين منفصلتين:
- حلقة لقراءة الأوامر المكتوبة على لوحة المفاتيح لإرسالها إلى الخادم. سيشير المستخدم إلى نهاية الأوامر باستخدام الكلمة الرئيسية "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 رسالة ترحيب عندما يتصل بها العميل:
- تحتوي بعض الخدمات على أمر "help" يوفر معلومات عن الأوامر المتاحة لتلك الخدمة. هذا ليس هو الحال هنا. أوامر SMTP المستخدمة في المثال هي كما يلي:
- mail from: sender، لتحديد عنوان البريد الإلكتروني للمرسل
- rcpt to: recipient، لتحديد عنوان البريد الإلكتروني لمستلم الرسالة. إذا كان هناك عدة مستلمين، يتم تكرار الأمر rcpt to: عدة مرات حسب الحاجة لكل مستلم.
- data الذي يشير إلى خادم SMTP بأن رسالة على وشك الإرسال. كما هو موضح في استجابة الخادم، تتكون هذه البيانات من سلسلة من الأسطر تنتهي بسطر يحتوي على نقطة فقط. قد تحتوي الرسالة على رؤوس مفصولة عن نص الرسالة بسطر فارغ. في مثالنا، قمنا بتضمين موضوع باستخدام الكلمة الرئيسية Subject:
- بمجرد إرسال الرسالة، يمكنك إخبار الخادم بأنك انتهيت باستخدام الأمر quit. ثم يقوم الخادم بإغلاق اتصال الشبكة. يمكن لسلسلة القراءة اكتشاف هذا الحدث والتوقف.
- ثم يكتب المستخدم "end" على لوحة المفاتيح لإيقاف مؤشر الترابط الذي يقرأ الأوامر المكتوبة على لوحة المفاتيح أيضًا.
إذا تحققنا من البريد الإلكتروني المستلم، فسنرى ما يلي (Outlook):

لاحظ أن خدمة SMTP لا يمكنها اكتشاف ما إذا كان المرسل صالحًا أم لا. لذلك، لا يمكنك أبدًا الوثوق بحقل "من" في الرسالة. هنا، لم يكن المرسل machin@univ-angers.fr موجودًا. يتيح لنا عميل TCP العام هذا اكتشاف بروتوكول اتصال خدمات الإنترنت، ومن ثم إنشاء فئات متخصصة لعملاء هذه الخدمات. دعونا نستكشف بروتوكول اتصال خدمة POP (بروتوكول مكتب البريد)، الذي يسمح للمستخدمين باسترداد رسائل البريد الإلكتروني المخزنة على الخادم. يعمل هذا البروتوكول على المنفذ 110.
dos>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]
يرسل عميل الويب أوامره إلى الخادم وفقًا للنمط التالي:
لا يستجيب خادم الويب إلا بعد استلام السطر الفارغ. في هذا المثال، استخدمنا أمرًا واحدًا فقط:
الذي يطلب عنوان URL /index.html من الخادم ويشير إلى أنه يستخدم الإصدار 1.0 من HTTP. أحدث إصدار من هذا البروتوكول هو 1.1. يوضح المثال أن الخادم استجاب بإرسال محتويات ملف index.html ثم أغلق الاتصال، كما نرى في قراءة الاستجابة التي تشير إلى إنهاء الخيط. قبل إرسال محتويات ملف index.html، أرسل خادم الويب سلسلة من الرؤوس تليها سطر فارغ:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
السطر <html> هو السطر الأول في ملف /index.html. يُطلق على النص الذي يسبقه اسم "رؤوس HTTP" (بروتوكول نقل النص التشعبي). لن نتطرق هنا إلى تفاصيل هذه الرؤوس، لكن ضع في اعتبارك أن عميلنا العام يتيح الوصول إليها، وهو ما قد يساعد في فهمها. على سبيل المثال، السطر الأول:
يشير إلى أن خادم الويب الذي تم الاتصال به يدعم بروتوكول HTTP/1.1 وأنه عثر بنجاح على الملف المطلوب (200 OK)، حيث 200 هو رمز استجابة HTTP. الأسطر
يخبر العميل أنه سيتلقى 11,251 بايت من نص HTML (لغة ترميز النص التشعبي) وأن الاتصال سيتم إغلاقه بمجرد إرسال البيانات. إذن لدينا هنا عميل TCP مفيد للغاية. في الواقع، هذا العميل موجود بالفعل على الأجهزة حيث يُسمى telnet، ولكن كان من المثير للاهتمام أن نكتبه بأنفسنا. برنامج عميل TCP العام هو كما يلي:
' 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:
الآن، لنفتح متصفحًا ونطلب عنوان URL http://localhost:88/exemple.html. سيتصل المتصفح عندئذٍ بالمنفذ 88 على جهاز localhost ويطلب الصفحة /example.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. يغلق الأمر end الاتصال من الخادم إلى العميل. في ردنا، اقتصرنا على رؤوس HTTP التالية:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
نحن لا نحدد حجم الملف الذي نرسله (Content-Length)، بل نكتفي بالإشارة إلى أننا سنغلق الاتصال (Connection: close) بعد إرساله. وهذا يكفي للمتصفح. فعندما يرى أن الاتصال قد أُغلق، سيدرك أن استجابة الخادم قد اكتملت، وسيقوم بعرض صفحة HTML التي أُرسلت إليه. وفيما يلي نص الصفحة:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
ثم يقوم المستخدم بإغلاق الاتصال بالعميل عن طريق كتابة الأمر "fin". عندئذٍ يدرك المتصفح أن استجابة الخادم قد اكتملت ويمكنه عرضها:

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

أي، بالضبط ما تم إرساله من الخادم العام. فيما يلي كود خادم TCP العام:
' 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 بـ 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
الاستجابة
يعني أن الصفحة المطلوبة قد تم نقلها (وبالتالي أصبح لها عنوان 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. البرنامج كالتالي:
' 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 الذي طلبه. أما العميل التالي فيتعامل معها.
- يقرأ السطر الأول من رؤوس HTTP المرسلة من الخادم للتحقق مما إذا كان يحتوي على السلسلة "302 Object moved"، والتي تشير إلى إعادة توجيه
- يقوم البرنامج بقراءة الرؤوس التالية. في حالة وجود إعادة توجيه، يبحث عن السطر "Location: 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>
البرنامج كالتالي:
' 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 الخاصة بكائن يتم تمرير معلمتين إليه:
- كائن TcpClient، الذي يسمح بالوصول إلى العميل
- كائن الضريبة 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 بمعالجة طلبات العملاء. ويمكن أن تتخذ هذه الطلبات شكلين:
- calculateMarried(y/n) numChildren annualSalary
- إنهاء الحسابات
النموذج 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
تم ترجمة هذه الفئة بواسطة
حيث تحتوي 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>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
السطر
يُنشئ كائن 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 أخرى:
يمكننا أن نرى أن العميل قد تلقى رسالة الترحيب من الخادم بنجاح. نرسل أوامر أخرى:
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]
نعود إلى نافذة الخادم لإيقافه:















