كيف تعمل بروتوكولات ويب سوكيت (WebSockets): دليل كامل للاتصالات في الوقت الفعلي

في الأيام الأولى للويب، كان المتصفح مجرد مستعرض مستندات بسيط. تطلب صفحة، ويقوم الخادم بمعالجتها وعرضها، ثم يغلق الاتصال. دورة الطلب والاستجابة هذه هي جوهر بروتوكول HTTP (Hypertext Transfer Protocol).

ومع ذلك، مع تطور تطبيقات الويب إلى تجارب غنية وتفاعلية—مثل غرف المحادثة الفورية، شاشات التداول المالي المباشرة، التحرير المشترك للمستندات، والألعاب متعددة اللاعبين—بدأ نموذج HTTP التقليدي يظهر حدوده وعيوبه.

للحصول على التحديثات المباشرة، اعتمد المطورون في البداية على حلول بديلة ومؤقتة:

  • الاستقصاء القصير (Short Polling): يرسل المتصفح بشكل متكرر طلبات HTTP إلى الخادم كل بضع ثوانٍ للسؤال عن وجود بيانات جديدة. يؤدي هذا إلى استهلاك هائل للنطاق الترددي للشبكة وإهدار موارد الخادم.
  • الاستقصاء الطويل (Long Polling / Comet): يرسل المتصفح طلباً، ويبقي الخادم الاتصال مفتوحاً حتى تتوفر بيانات جديدة. بمجرد إرسال البيانات، يتم إغلاق الاتصال ويفتح المتصفح فوراً طلباً جديداً. هذا الأسلوب معقد في الإدارة ولا يزال يتطلب حملاً برمجياً كبيراً لإنشاء كل اتصال.

جاءت تقنية ويب سوكيت (WebSockets) لحل هذه العقبات من خلال تقديم بروتوكول قياسي للاتصال المستمر، ثنائي الاتجاه، والكامل (full-duplex) عبر اتصال TCP واحد ومستمر.


ما هو بروتوكول ويب سوكيت (WebSocket)؟

تعمل اتصالات ويب سوكيت (المحددة في RFC 6455) جنباً إلى جنب مع بروتوكول HTTP. بينما HTTP هو بروتوكول عديم الحالة (stateless) حيث يمكن للعميل فقط بدء الطلبات، فإن اتصال ويب سوكيت يظل مفتوحاً لأجل غير مسمى بمجرد إنشائه، مما يسمح لكل من العميل والخادم بإرسال البيانات لبعضهما البعض في أي وقت وبأقل زمن تأخير ممكن.

إليك القاعدة الأساسية لبروتوكول ويب سوكيت:

بمجرد إنشاء الاتصال، يمكن لأي من الطرفين إرسال الرسائل في أي وقت دون الحاجة إلى بدء طلب اتصال جديد.


خطوة بخطوة: دورة حياة اتصال ويب سوكيت

يمر اتصال ويب سوكيت بثلاث مراحل متميزة: المصافحة (Handshake)، نقل البيانات، وإغلاق الاتصال.

WebSocket Connection Lifecycle Diagram

1. مصافحة HTTP (ترقية البروتوكول)

نظراً لأن جدران الحماية وأجهزة التوجيه مهيأة للسماح بمرور حركة مرور ويب القياسية على المنافذ 80 (HTTP) و 443 (HTTPS)، تبدأ اتصالات ويب سوكيت رحلتها كطلب HTTP/1.1 قياسي. يسمى هذا بـ مصافحة الترقية (Upgrade Handshake).

طلب العميل

يرسل العميل طلب HTTP GET مع ترويسات (headers) محددة تطلب تبديل البروتوكول:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
  • Upgrade: websocket و Connection: Upgrade: تخبر الخادم أن العميل يرغب في تبديل البروتوكول.
  • Sec-WebSocket-Key: قيمة عشوائية بحجم 16 بايت مشفرة بترميز Base64. تُستخدم لإثبات أن الخادم قد تلقى طلب المصافحة ويفهم بروتوكول ويب سوكيت.
  • Sec-WebSocket-Version: تحدد إصدار بروتوكول ويب سوكيت (عادة ما يكون الإصدار 13).
  • Origin: تُستخدم من قِبل الخادم لتحديد ما إذا كان سيسمح بالاتصال (فحص أمني ضد المواقع غير المصرح لها).

استجابة الخادم

إذا كان الخادم يدعم ويب سوكيت، فإنه يقوم بالتحقق من صحة الطلب ويستجيب برمز حالة HTTP 101 Switching Protocols:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • كيف يقوم الخادم بحساب قيمة Sec-WebSocket-Accept:
    1. يأخذ الخادم قيمة ترويسة Sec-WebSocket-Key المرسلة من العميل (dGhlIHNhbXBsZSBub25jZQ==).
    2. يقوم بدمجها مع المعرف الفريد القياسي (GUID) السحري: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
    3. يحسب قيمة التجزئة SHA-1 للسلسلة النصية المدمجة.
    4. يقوم بتشفير التجزئة الناتجة بترميز Base64.
    5. إذا تحقق العميل من تطابق هذه القيمة مع توقعاته، تنجح المصافحة، ويتحول اتصال HTTP إلى مقبس TCP خام، وينتقل الطرفان إلى استخدام بروتوكول ويب سوكيت.

2. تقسيم ونقل البيانات

على عكس بروتوكول HTTP الذي يرسل ترويسات نصية تليها هيئة الطلب، يرسل بروتوكول ويب سوكيت البيانات في حزم ثنائية مهيكلة تسمى الإطارات (frames).

يحتوي إطار ويب سوكيت على ترويسة خفيفة جداً (تتراوح بين 2 إلى 14 بايت) تليها الحمولة (payload). تحتوي هذه الترويسة على:

  • بت FIN (بت واحد): يحدد ما إذا كان هذا الإطار هو الإطار الأخير في الرسالة.
  • Opcode (4 بت): يحدد نوع الإطار:
    • 0x1: إطار نصي (مشفّر بترميز UTF-8)
    • 0x2: إطار ثنائي
    • 0x8: طلب إغلاق الاتصال
    • 0x9: إشارة Ping (طلب نبض القلب)
    • 0xA: إشارة Pong (استجابة نبض القلب)
  • بت القناع (بت واحد): يحدد ما إذا كانت بيانات الحمولة مشفرة بقناع.
  • طول الحمولة (Payload Length): حجم البيانات.
  • مفتاح القناع (4 بايت): متطلب أمني بالغ الأهمية: يجب تشفير جميع الإطارات المرسلة من العميل إلى الخادم بقناع (باستخدام عملية XOR) ومفتاح عشوائي بحجم 4 بايت. هذا يمنع الخوادم الوسيطة (الوكيلة) من قراءة البيانات أو التلاعب بها. في المقابل، يجب ألا تُشفر الإطارات المرسلة من الخادم إلى العميل بقناع.

نبضات القلب (Ping/Pong)

لمنع أجهزة التوجيه وموزعات الأحمال من إغلاق الاتصالات الخاملة، يمكن لأي من الطرفين إرسال إطار Ping. ويجب على الطرف المتلقي الرد فوراً بإطار Pong يحتوي على نفس الحمولة.

3. إغلاق الاتصال

لإغلاق الاتصال بشكل نظيف:

  1. يرسل أحد الطرفين إطار Close يحتوي على رمز الحالة (مثل 1000 للإغلاق العادي، أو 1006 للإغلاق غير العادي) وسبب نصي اختياري.
  2. يستجيب الطرف الآخر بإرسال إطار Close الخاص به.
  3. يتم إغلاق مقبس TCP الأساسي.

مثال على الكود: تطبيق ويب سوكيت باستخدام Node.js

لمشاهدة ويب سوكيت قيد التشغيل، دعنا نكتب تطبيقاً بسيطاً بلغة Node.js. سنقوم بإنشاء خادم ويب سوكيت محلي يعيد إرسال أي رسالة يتلقاها، إلى جانب كود العميل للاتصال به.

خادم ويب سوكيت (server.js)

const { WebSocketServer } = require('ws');
const http = require('http');

// 1. إنشاء خادم HTTP قياسي
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('HTTP Server running. Use WebSocket to connect.\n');
});

// 2. ربط خادم ويب سوكيت بخادم HTTP
const wss = new WebSocketServer({ server });

wss.on('connection', (ws, req) => {
    const clientIp = req.socket.remoteAddress;
    console.log(`[الخادم] عميل جديد متصل من العنوان: ${clientIp}`);

    // إرسال رسالة ترحيبية للعميل
    ws.send(JSON.stringify({ type: 'welcome', message: 'مرحباً بك! لقد اتصلت بخادم ويب سوكيت Ghaznix!' }));

    // الاستماع للرسائل الواردة من العميل
    ws.on('message', (message) => {
        console.log(`[الخادم] رسالة مستلمة: ${message}`);
        
        try {
            const data = JSON.parse(message);
            ws.send(JSON.stringify({
                type: 'echo',
                message: `رد الخادم: ${data.text.toUpperCase()}`,
                timestamp: new Date().toISOString()
            }));
        } catch (e) {
            ws.send(JSON.stringify({ type: 'error', message: 'تنسيق JSON غير صالح' }));
        }
    });

    // التعامل مع قطع اتصال العميل
    ws.on('close', (code, reason) => {
        console.log(`[الخادم] تم قطع اتصال العميل (الرمز: ${code}، السبب: ${reason.toString() || 'لا يوجد'})`);
    });

    ws.on('error', (error) => {
        console.error(`[الخادم] خطأ في المقبس: ${error.message}`);
    });
});

server.listen(8080, () => {
    console.log('خادم ويب سوكيت يستمع على المنفذ: ws://localhost:8080');
});

عميل المتصفح (كود جافا سكريبت من جانب العميل)

يمكنك تشغيل هذا العميل مباشرة في وحدة تحكم المتصفح (Console):

// 1. إنشاء اتصال مع الخادم
const socket = new WebSocket('ws://localhost:8080');

// 2. معالج فتح الاتصال بنجاح
socket.addEventListener('open', (event) => {
    console.log('[العميل] تم الاتصال بالخادم بنجاح.');
    
    const payload = JSON.stringify({ text: 'مرحباً أيها الخادم!' });
    socket.send(payload);
    console.log(`[العميل] تم الإرسال: ${payload}`);
});

// 3. الاستماع للرسائل المستلمة من الخادم
socket.addEventListener('message', (event) => {
    const response = JSON.parse(event.data);
    console.log('[العميل] رسالة جديدة مستلمة من الخادم:', response);
});

// 4. معالج إغلاق الاتصال
socket.addEventListener('close', (event) => {
    console.log(`[العميل] تم إغلاق الاتصال (الرمز: ${event.code})`);
});

// 5. الاستماع للأخطاء
socket.addEventListener('error', (error) => {
    console.error('[العميل] خطأ في ويب سوكيت:', error);
});

مقارنة تفصيلية: بروتوكول HTTP مقابل ويب سوكيت

الميزة HTTP/1.1 ويب سوكيت (WebSockets)
اتجاه الاتصال أحادي الاتجاه (يبدأ بطلب العميل) ثنائي الاتجاه (من العميل أو الخادم)
نموذج الاتصال طلب واستجابة (قصير الأجل) اتصال مستمر (طويل الأجل)
الحمل الإضافي للشبكة مرتفع (إرسال الترويسات مع كل طلب) منخفض جداً (ترويسة إطار خفيفة للغاية)
حالة الاتصال عديم الحالة (Stateless) ذو حالة (يتم الحفاظ على سياق الاتصال)
البروتوكول المستخدم http:// أو https:// ws:// أو wss://
أفضل استخدام جلب صفحات الويب، خدمات REST APIs المحادثات المباشرة، شاشات البيانات الفورية، البث

الاعتبارات الأمنية لبروتوكول ويب سوكيت

نظراً لأن اتصالات ويب سوكيت تتجاوز مسارات توجيه HTTP التقليدية بعد اكتمال المصافحة، فإنها تقدم ثغرات أمنية فريدة يجب الحذر منها:

  1. استخدم بروتوكول ويب سوكيت الآمن (wss://): احرص دائماً على تشغيل الاتصالات عبر بروتوكول مشفر TLS/SSL (على المنفذ 443). يقوم WSS بتشفير حمولة الإطار، مما يمنع التنصت والتلاعب بالبيانات.
  2. التحقق من مصدر الطلب (Origin): لا تخضع اتصالات ويب سوكيت لسياسة نفس المصدر (Same-Origin Policy). يجب عليك دائماً التحقق من قيمة ترويسة Origin على الخادم أثناء المصافحة لمنع الهجمات والمواقع غير المصرح لها.
  3. المصادقة أثناء المصافحة: تحقق من هوية العميل قبل استكمال عملية الاتصال. يتم ذلك عادة عبر تمرير رمز مميز (مثل JWT) في معلمات الاستعلام، أو التحقق من ملفات تعريف الارتباط للجسلة (Session Cookies).
  4. تطهير البيانات المدخلة: تعامل مع كل رسالة تصل عبر ويب سوكيت كبيانات غير موثوقة. قم بالتحقق من البيانات وتطهيرها لمنع هجمات حقن البرمجيات الخبيثة عبر المواقع (XSS).

ملخص

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


استكشف المزيد من الدروس والأدلة البرمجية على مدونة Ghaznix →