برنامجان يتكلّمان عبر TCP
حتّى الآن تكلّمنا عن العناوين. اليوم نكتب برنامجين بـ Python: خادم (server) ينتظر، وعميل (client) يتّصل به ويرسل رسالة ويستلم ردّاً.
نستعمل بروتوكول TCP: يفتح اتّصالاً أوّلاً، ثمّ يضمن أنّ البايتات تصل كاملة وبالترتيب. سنقرأ الكود سطراً سطراً، ثمّ نشغّل البرنامجين معاً خطوةً خطوة ونرى ما يمرّ على الشبكة.
عنوان IP + منفذ
الـ socket نقطة نهاية للاتّصال، يُعرَّف بعنوان IP ورقم منفذ (port). العنوان يوصل للجهاز، والمنفذ يوصل للبرنامج داخله.
الخادم هنا على 127.0.0.1 (الجهاز نفسه، Loopback) والمنفذ 3450. والعميل يأخذ منفذاً عشوائيّاً يختاره النظام.
socket.socket(AF_INET, SOCK_STREAM): AF_INET عناوين IPv4، وSOCK_STREAM يعني TCP.الخادم
127.0.0.1:3450
ثابت ومعروف، ينتظر.
العميل
127.0.0.1:51544
منفذ مؤقّت، يبدأ الاتّصال.
الخادم: bind ← listen ← accept
يربط نفسه بعنوان ومنفذ، ثمّ يستمع، ثمّ ينتظر عميلاً. لكلّ عميل حلقة داخليّة تقرأ ما يصل وتردّ عليه، حتّى يُغلق العميل.
العميل: connect ← send ← recv
العميل أبسط: يتّصل بعنوان الخادم ومنفذه، يرسل رسالتين، يقرأ الردّ مرّة واحدة، ثمّ يغلق ويطبع.
شغّل الخادم والعميل خطوةً خطوة
TCP تيّار بايتات، لا رسائل
العميل يستدعي send مرّتين، لكنّ TCP لا يحفظ أين تنتهي كلّ رسالة: قد تصل البايتات في دفعتين أو دفعة واحدة، حسب سرعة الشبكة والتوقيت.
لهذا يجمع الخادم في from_client ولا يعتمد على أنّ كلّ recv رسالة كاملة. والبرامج الحقيقيّة تضع فاصلاً (مثل \n) أو طول الرسالة أوّلها.
"I am CLIENT\n""hello"الخادم يردّ 2 مرّتين بـ I am SERVER (ردّ لكلّ recv)، فيطبع العميل سطرين (إن وصل الردّان قبل أن يقرأ).
أشياء ستصادفها في المخبر
- شغّل الخادم أوّلاً. إن شغّلت العميل قبله:
ConnectionRefusedError، لأنّ لا أحد يستمع على المنفذ 3450. - الخادم يخدم عميلاً واحداً في كلّ مرّة: ما دام في الحلقة الداخليّة لا يعود إلى
accept. العميل الثاني ينتظر في طابورlisten(5). - أعدت تشغيل الخادم فظهر
OSError: Address already in use؟ المنفذ ما زال محجوزاً لحظات بعد الإغلاق. انتظر قليلاً أو أضف قبل bind:serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - السطر الأخير في server.py لا يُطبع أبداً، والخادم يتوقّف بـ
Ctrl + C. - العميل يقرأ مرّة واحدة: إن تأخّر الردّ الثاني فقد يطبع
I am SERVERمرّة واحدة فقط.
طرفيّتان على جهاز واحد
- افتح طرفيّة في مجلّد الجلسة وشغّل الخادم:
python server.py. لن يطبع شيئاً: هو ينتظر عندaccept. - افتح طرفيّة ثانية وشغّل العميل:
python client.py، واكتب رسالة. - ستطبع طرفيّة الخادم ما وصل، وطرفيّة العميل الردّ. شغّل العميل مرّة أخرى: الخادم ما زال يعمل.
- تحدٍّ: غيّر المنفذ في أحد الملفّين فقط. ماذا يحدث؟ ولماذا؟