الشبكات وHTTP

أساسيات الشبكات

15 دقيقة الدرس 1 من 13

أساسيات الشبكات

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

حزمة بروتوكولات TCP/IP

TCP/IP ليس بروتوكولًا واحدًا؛ بل هو مجموعة مكوّنة من أربع طبقات. من الأدنى إلى الأعلى:

  • طبقة الوصول إلى الشبكة (الوصل) — الإرسال المادي: إطارات Ethernet، وحزم Wi-Fi. لا تتعامل Java مع هذه الطبقة مباشرة.
  • طبقة الإنترنت (IP) — تُوجّه الحزم عبر الشبكات باستخدام عناوين IP. تتنقّل كل حزمة بشكل مستقل وقد تصل خارج الترتيب أو لا تصل أصلًا. البروتوكول IP يعمل على أساس بذل أفضل جهد.
  • طبقة النقل — تُوصّل البيانات إلى العملية الصحيحة. يعيش هنا بروتوكولان: TCP وUDP.
  • طبقة التطبيق — البروتوكولات التي ينفّذها تطبيقك فوق طبقة النقل: HTTP/2، وTLS، وWebSocket، وSMTP، وغيرها.
عناوين IP تُعرّف الأجهزة؛ المنافذ تُعرّف العمليات. عندما تصل حزمة إلى جهاز ما، يقرأ نظام التشغيل رقم المنفذ الوجهة من ترويسة طبقة النقل ويسلّم البيانات إلى العملية التي تستمع على ذلك المنفذ. بدون المنافذ، لن يتمكّن جهاز واحد من تشغيل أكثر من برنامج شبكي واحد في آنٍ واحد.

TCP: موثوق ومُرتَّب وقائم على الاتصال

يضيف بروتوكول TCP (بروتوكول التحكم في النقل) ثلاث ضمانات فوق IP الخام:

  1. التسليم الموثوق — تُعاد إرسال الحزم المفقودة تلقائيًا.
  2. التسليم المُرتَّب — تصل البايتات بالترتيب الذي أُرسلت به تمامًا.
  3. التحكم في التدفق والازدحام — يتم تقييد سرعة المُرسِل لتتناسب مع ما يستطيع المستقبل والشبكة استيعابه.

تأتي هذه الضمانات من المصافحة الثلاثية التي تُنشئ الاتصال قبل أن تتدفق أي بيانات للتطبيق:

// ما يحدث تحت الغطاء عند استدعاء new Socket("host", port): // // العميل الخادم // |--- SYN (seq=x) ------------>| "أريد الاتصال" // |<-- SYN-ACK (seq=y,ack=x+1)-| "حسنًا، أؤكّد؛ هذا تسلسلي" // |--- ACK (ack=y+1) ---------->| "مؤكَّد — الاتصال مُنشأ" // | // | ... تتدفق بيانات التطبيق في كلا الاتجاهين ... // | // |--- FIN ----------------------| "انتهيتُ من الإرسال" // |<-- FIN-ACK -----------------| // |--- ACK ----------------------| (إغلاق رباعي)

كل Socket تُنشئه في Java يُطلق هذه المصافحة. وكل استدعاء لـ socket.close() يُطلق الإغلاق الرباعي. هذه الرحلات ذهابًا وإيابًا لها تكلفة زمنية — ولهذا أدخل HTTP/1.1 مفهوم keep-alive وأدخل HTTP/2 التعدد (multiplexing): لإعادة استخدام الاتصالات بدلًا من إعادة بنائها لكل طلب.

المنافذ وتعيينات المنافذ المعروفة

تُقسَّم نطاقات أرقام المنافذ ذات 16 بت (0–65535) بحسب الاتفاقية:

  • 0–1023: المنافذ المعروفة — مُعيَّنة من قِبَل IANA. HTTP = 80، HTTPS = 443، SSH = 22، SMTP = 25. يتطلّب الربط بهذه المنافذ صلاحيات المسؤول (root) في معظم الأنظمة.
  • 1024–49151: المنافذ المُسجَّلة — تستخدمها بروتوكولات التطبيقات المعروفة دون الحاجة إلى صلاحيات مرتفعة. MySQL = 3306، PostgreSQL = 5432، Redis = 6379.
  • 49152–65535: المنافذ الديناميكية / المؤقتة — يُعيّنها نظام التشغيل تلقائيًا لـجانب العميل من الاتصال. عندما يتصل عميل Java بخادم، يختار نظام التشغيل منفذًا مؤقتًا كمنفذ مصدر حتى يتمكّن الخادم من إرسال الردود إلى ذلك المقبس تحديدًا.
اختر منفذًا خارج النطاق 0–1023 لخوادمك الخاصة أثناء التطوير حتى لا تحتاج إلى رفع الصلاحيات. الممارسة القياسية هي جعل المنفذ قابلًا للتهيئة عبر متغير بيئة أو ملف إعدادات بدلًا من تثبيته في الكود.

نموذج العميل/الخادم

يتبع كل شبكات Java المعتمدة على TCP نفس التقسيم البنيوي:

  • الخادم — يرتبط بمنفذ معيّن على واجهة محدّدة، ويدخل حالة الاستماع، ويقبل طلبات الاتصال الواردة. إنه سلبي.
  • العميل — يعرف عنوان IP الخادم ومنفذه، ويبادر بالاتصال بشكل نشط، ويتواصل بمجرد اكتمال المصافحة.

في Java، يمثّل ServerSocket نقطة الاستماع؛ وكل استدعاء لـ serverSocket.accept() يحجب حتى يتصل عميل ثم يُعيد Socket عاديًا يمثّل ذلك الاتصال المحدد. يُسلّم الخادم عادةً هذا المقبس إلى خيط (thread) — أو خيط افتراضي في Java 21+— حتى يتمكّن من قبول العميل التالي فورًا.

// مخطط مفاهيمي — فقط لرؤية شكل الواجهة البرمجية // (التنفيذ الكامل مُغطَّى في الدرس الثاني) // جانب الخادم ServerSocket server = new ServerSocket(8080); Socket client = server.accept(); // يحجب حتى يتصل عميل InputStream in = client.getInputStream(); OutputStream out = client.getOutputStream(); // ... قراءة الطلب، كتابة الاستجابة ... client.close(); // جانب العميل Socket socket = new Socket("localhost", 8080); // يُطلق مصافحة TCP InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); // ... كتابة الطلب، قراءة الاستجابة ... socket.close();

عناوين IP: IPv4 وIPv6

تجرّد فئة InetAddress في Java كلا عائلتَي العناوين:

import java.net.InetAddress; import java.net.UnknownHostException; public class AddressDemo { public static void main(String[] args) throws UnknownHostException { // تحليل اسم المضيف إلى عناوين IP InetAddress[] addresses = InetAddress.getAllByName("api.example.com"); for (InetAddress addr : addresses) { System.out.println(addr.getHostAddress()); // "93.184.216.34" (IPv4) أو IPv6 } // العنوان المحلي (loopback) — دائمًا متاح ولا يغادر الجهاز أبدًا InetAddress loopback = InetAddress.getLoopbackAddress(); System.out.println(loopback); // 127.0.0.1 أو ::1 // الواجهة الشاملة (Wildcard) — الربط بجميع الواجهات (شائع للخوادم) // استخدم null أو "0.0.0.0" / "::" عند إنشاء ServerSocket } }
تحليل DNS عملية حاجبة وقد تفشل. ينفّذ InetAddress.getByName() بحث DNS على الخيط المُستدعي. في الخوادم عالية الأداء، يُوقف هذا الخيوط. يتعامل HttpClient الحديث مع هذا داخليًا بشكل غير متزامن؛ مع المقابس الخام يجب عليك نقل بحوث DNS إلى خيوط منفصلة أو استخدام مكتبة DNS غير متزامنة.

المقابس موارد إدخال/إخراج — أغلقها دائمًا

كل من Socket وServerSocket ينفّذ AutoCloseable. استخدم دائمًا try-with-resources. المقبس غير المغلق يحتجز واصفًا لملف نظام التشغيل وفتحة اتصال TCP؛ تسريب المقابس تحت الضغط سيُنهك كليهما.

// النمط الصحيح: try-with-resources يضمن استدعاء close() في أي مسار خروج try (ServerSocket server = new ServerSocket(8080); Socket client = server.accept()) { // التواصل مع العميل ... } // كلا المقبسين مغلقان هنا حتى لو رُمي استثناء

TCP مقابل UDP: متى تستحق تكلفة الموثوقية؟

ضمانات موثوقية TCP ليست مجانية: تُضيف إعادة الإرسال زمن استجابة، وحجب رأس الصف (head-of-line blocking) يعني أن حزمة مفقودة تُوقف جميع البيانات اللاحقة على ذلك الاتصال. يُقايض UDP الموثوقية بالسرعة:

  • لا إعداد للاتصال — أرسل أول حزمة فورًا.
  • لا إعادة إرسال — البيانات المفقودة تضيع نهائيًا.
  • لا ترتيب — قد تصل الحزم بأي تسلسل.

استخدم UDP عندما تتفوّق التوقيت على الصحة: بث الفيديو/الصوت المباشر، وتحديثات حالة الألعاب الإلكترونية، واستعلامات DNS، وبروتوكول QUIC (الناقل تحت HTTP/3). لواجهات REST البرمجية، واتصالات قواعد البيانات، ونقل الملفات، ومعظم أعباء عمل المؤسسات، يظل TCP الخيار الافتراضي الصحيح.

الخلاصة

TCP/IP هو مجموعة طبقات حيث يُوجّه IP الحزم ويجعل TCP التسليم موثوقًا ومُرتَّبًا على حساب زمن استجابة المصافحة. تربط المنافذ نقطة نهاية النقل بعملية محدّدة. يُقسّم نموذج العميل/الخادم الشبكات إلى مُتصل نشط ومُستمع سلبي — مُعكوسان بالضبط بواسطة Socket وServerSocket في Java. هذه ليست مجرد مفاهيم نظرية: كل مهلة تضبطها، وكل حوض اتصالات تُحجّمه، وكل سياسة إعادة محاولة تُصمّمها، هي نتيجة مباشرة لفهم كيفية عمل TCP على هذا المستوى.