الشبكات والاتصال

WebSockets والتواصل الفوري

18 دقيقة الدرس 7 من 10

WebSockets والتواصل الفوري

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

مصافحة WebSocket (الترقية)

يبدأ اتصال WebSocket كطلب HTTP/1.1 عادي تمامًا. يرسل العميل ترويسة Upgrade تُعلن نيّته، فيرد الخادم بـ 101 Switching Protocols، ومن تلك اللحظة يُعاد توظيف اتصال TCP الأساسي — يُستبدل إطار HTTP ببروتوكول إطارات WebSocket الخفيف (RFC 6455). لا مصافحة TCP جديدة، ولا إعادة تفاوض TLS؛ الترقية تتم في مكانها.

WebSocket upgrade handshake and full-duplex message flow Client Server — HTTP Upgrade Phase — GET /ws Upgrade: websocket 101 Switching Protocols — Full-Duplex WS Frames — Frame: "subscribe chat:room42" Frame: { msg: "Alice: hello!" } Frame: { msg: "Bob: hi!" } Frame: "send: hey everyone" Frame: { msg: "You: hey everyone" } Close frame (code 1000)
مصافحة ترقية WebSocket يتبعها تبادل إطارات ثنائي الاتجاه. أي طرف يستطيع الإرسال في أي وقت، وأي طرف يستطيع بدء إغلاق الاتصال.

الأرقام الجوهرية: حجم ترويسة إطار WebSocket هو 2 إلى 14 بايت فقط لكل رسالة، مقارنةً بـ 400–900 بايت من ترويسات HTTP التي ترافق كل طلب استطلاع. عند معدلات رسائل عالية — محطة تداول تستقبل 1000 تحديث سعر في الثانية — هذا الفارق ليس نظريًا بل جوهري تمامًا.

متى يُعدّ التواصل الفوري ضرورة حقيقية؟

ليست كل ميزة "مباشرة" تستوجب WebSockets. قبل اختيار التقنية، حدّد متطلبات الكُمون والتكرار:

  • الدردشة والمراسلة — Slack، WhatsApp Web. يتوقع المستخدمون ظهور الرسائل في أقل من ثانية. عدد اتصالات ضخم (ملايين الاتصالات المستمرة)، بمعدل رسائل متوسط لكل مستخدم.
  • التحرير التشاركي — Google Docs، Figma. يجب أن تتزامن مواضع المؤشرات وعمليات الدلتا بين جميع المحررين بكُمون يُقاس بعشرات الميلي‍ثانية. أي عملية فائتة تُفسد اتساق المستند.
  • التداول المباشر وبيانات السوق — Bloomberg Terminal، Robinhood. يمكن أن تصل عروض الأسعار آلاف المرات في الثانية لكل ورقة مالية. أي عبء لكل رسالة HTTP يُصبح محظورًا.
  • الألعاب متعددة اللاعبين — مواضع اللاعبين، الصحة، حالة الفيزياء. الكُمون فوق ~100 ميلي‍ثانية محسوس ويُدمّر تجربة اللعب.
  • لوحات التحكم المباشرة والمراقبة — لوحات Grafana المباشرة، سجلات النشر المتدفقة إلى المتصفح. الخادم يدفع البيانات؛ العميل نادرًا ما يرسل شيئًا. SSE يمكن أن يؤدي الغرض هنا أيضًا.
  • الإشعارات — "شُحنت طلبيتك". نادرة، أحادية الاتجاه. تدفق SSE بسيط أو حتى Long Polling يكفي؛ WebSockets الكاملة قد تكون مبالغة.
السؤال الجوهري: هل يحتاج العميل إلى إرسال بيانات للخادم بتكرار عالٍ، أم استقبالها فقط؟ WebSockets كاملة الاتجاهين تتألق حين يكون الحركة مكثفة في كلا الاتجاهين. إذا كانت الحركة في معظمها من الخادم إلى العميل، فإن SSE أبسط ومتوافق مع HTTP/2.

المعمارية: توسيع نطاق خوادم WebSocket

يمكن لعملية خادم WebSocket واحدة أن تحتفظ بعشرات الآلاف من الاتصالات المتزامنة، لكن الأنظمة الحقيقية تحتاج خوادم متعددة. هذا يُفرز مشكلة أساسية: رسالة أرسلها العميل A (المتصل بالخادم 1) يجب أن تصل إلى العميل B (المتصل بالخادم 2).

الحل المعياري هو بنية Pub/Sub الخلفية — Redis Pub/Sub عادةً أو وسيط رسائل. كل خادم يشترك في القنوات التي تخص عملاءه المتصلين. حين تصل رسالة إلى أي خادم، يُنشرها على الطبقة الخلفية؛ تقوم الطبقة بتوزيعها على جميع الخوادم الأخرى التي تُعيد إيصالها إلى عملائها المستهدفين.

Scaling WebSocket servers with a Pub/Sub backplane Load Balancer WS Server 1 Client A, C, D… WS Server 2 Client B, E, F… WS Server 3 Client G, H… Redis Pub/Sub Backplane * يستخدم موزع الحمل جلسات لاصقة (تجزئة ثابتة بمعرف المستخدم) لضمان عودة الاتصالات إلى نفس الخادم.
ثلاثة خوادم WebSocket خلف موزع حمل، جميعها متصلة بطبقة Redis Pub/Sub الخلفية. رسالة تُنشر على الخادم 1 تصل إلى عملاء الخادمين 2 و3 عبر الطبقة الخلفية.
الجلسات اللاصقة مهمة: موازنة الحمل بالدوري (Round Robin) القياسية تُكسر إعادة اتصالات WebSocket — قد يهبط العميل على خادم مختلف ويفقد حالة اشتراكه. استخدم التجزئة الثابتة بمعرف المستخدم أو الجلسات اللاصقة (تجزئة IP، ارتباط بملف تعريف الارتباط) لضمان عودة العميل إلى نفس الخادم عند إعادة الاتصال، أو احتفظ بحالة الجلسة في مخزن مشترك.

إدارة الاتصالات والنبضات القلبية (Heartbeats)

اتصال TCP المستمر غير مرئي لمعدات الشبكة — تملك جدران الحماية وأجهزة NAT مؤقتات للاتصالات الخاملة، تتراوح عادةً بين 30–300 ثانية، تُسقط الاتصال بصمت بعدها. العميل والخادم يظلان غير مدركَين لذلك حتى يحاول أحدهما إرسال رسالة ولا يتلقى أي إقرار.

الحل هو نبضة قلبية Ping/Pong: يرسل الخادم إطار تحكم Ping عبر WebSocket كل 20–30 ثانية؛ ويُلزم البروتوكول العميلَ بالرد بـ Pong. إن لم يصل Pong خلال نافذة المهلة، يُغلق الخادم الاتصال. العميل، حين يلاحظ الإغلاق، يُطلق إعادة اتصال بتراجع أسي — عادةً يبدأ بثانية واحدة، يتضاعف حتى حد أقصى 30–60 ثانية، مع تشويش عشوائي صغير لتجنب إعادة اتصال آلاف العملاء في آنٍ واحد (مشكلة القطيع المتدافع).

اعتبارات الأمان

يجب دائمًا استخدام wss:// (مشفر بـ TLS، مكافئ HTTPS) لاتصالات WebSocket. مصافحة HTTP الأولية خاضعة لنفس سياسة المصدر الواحد كالطلبات العادية، لكن الاتصال المستمر بعدها لا يخضع لها — بمجرد إنشاء الاتصال، لا يُطبق المتصفح فحوصات المصدر على الإطارات. هذا يعني:

  • المصادقة عند الاتصال — تحقق من رمز أو ملف تعريف ارتباط الجلسة خلال طلب ترقية HTTP، قبل فتح WebSocket. لا تعتمد على المصادقة في النطاق لاحقًا.
  • التحقق من كل رسالة — عامل رسائل WebSocket الواردة من العملاء كمدخلات غير موثوقة تمامًا كأجسام طلبات HTTP. عقّمها وتحقق منها.
  • تحديد معدل الاتصالات والرسائل — عميل يفتح آلاف الاتصالات أو يرسل ملايين الإطارات الصغيرة في الثانية هو ناقل هجوم حجب خدمة. طبّق الحدود في طبقة خادم WS أو البوابة.
لا تضع بيانات المصادقة في عنوان URL. من الشائع رؤية wss://api.example.com/ws?token=SECRET — هذا يُسرّب الرمز إلى سجلات الوصول، وسجلات الوكيل، وسجل المتصفح. مرّر الرمز في ملف تعريف ارتباط مُعيَّن أثناء تسجيل الدخول (يُرسل تلقائيًا مع طلب الترقية) أو ضمن إطار الرسالة الأول بعد فتح الاتصال.

WebSockets مقابل البدائل

عمليًا، يقف WebSocket جنبًا إلى جنب مع تقنيتين أخريين لدفع البيانات من الخادم. الاختيار الصحيح يعتمد على نمط حركة البيانات:

  • Long Polling — يرسل العميل طلب HTTP؛ يبقي الخادم الطلب مفتوحًا حتى تتوفر بيانات، ثم يستجيب. يرسل العميل فورًا طلبًا جديدًا. يعمل في كل مكان، لكنه عالي الكُمون والعبء، وليس ثنائي الاتجاه حقًا.
  • Server-Sent Events (SSE) — تدفق استجابة HTTP طويل الأمد واحد؛ يدفع الخادم أحداثًا نصية محددة بأسطر جديدة. مدعوم أصلًا في المتصفحات، متعدد القنوات في HTTP/2، مثالي للتغذيات أحادية الاتجاه. لا مسار من العميل إلى الخادم.
  • WebSockets — كاملة الاتجاهين، إطارات ثنائية أو نصية، عبء منخفض. ضرورية حين يرسل العميل بتكرار (مدخلات الألعاب، تعديلات تشاركية، مؤشرات الكتابة في الدردشة).

الدرس التاسع من هذه السلسلة يغطي المقارنة الكاملة بالتفصيل. حتى ذلك الحين: إن كنت بحاجة إلى تواصل ثنائي الاتجاه بكُمون منخفض وتكرار عالٍ — WebSockets هو الأداة المناسبة.

مقياس العالم الحقيقي: تحتفظ Slack بنحو 10 ملايين اتصال WebSocket متزامن لخدمة قاعدة مستخدميها العالمية. أفاد Discord بمعالجة أكثر من 100 مليون رسالة WebSocket في الثانية عبر بنيته التحتية. كلاهما يستخدم Redis Pub/Sub (أو ما يعادله) كطبقة خلفية بين عُقد بوابة WebSocket.