وب‌سوکت (WebSockets) چگونه کار می‌کند: راهنمای کامل ارتباطات بلادرنگ (Real-Time)

در روزهای اولیه وب، مرورگر یک نمایشگر اسناد ساده بود. شما یک صفحه را درخواست می‌کردید، سرور آن را رندر می‌کرد و اتصال بسته می‌شد. این چرخه درخواست-پاسخ هسته اصلی HTTP (Hypertext Transfer Protocol) است.

با این حال، با تکامل برنامه‌های وب به تجربه‌های غنی و تعاملی — مانند چت بلادرنگ، نمودارهای مالی زنده، ویرایش مشارکتی و بازی‌های چندنفره — مدل سنتی HTTP شروع به نشان دادن محدودیت‌های خود کرد.

برای دریافت به‌روزرسانی‌های زنده، توسعه‌دهندگان در ابتدا به راه‌حل‌های موقتی متکی بودند:

  • Short Polling (نظرسنجی کوتاه): مرورگر به طور مکرر هر چند ثانیه یک بار درخواست‌های HTTP را به سرور ارسال می‌کند تا داده‌های جدید را جویا شود. این کار پهنای باند زیادی را هدر داده و منابع سرور را تلف می‌کند.
  • Long Polling (نظرسنجی طولانی / Comet): مرورگر یک درخواست ارسال می‌کند و سرور اتصال را تا زمانی که داده‌های جدید در دسترس باشد باز نگه می‌دارد. به محض ارسال داده‌ها، اتصال بسته می‌شود و مرورگر بلافاصله یک درخواست جدید باز می‌کند. مدیریت این روش پیچیده است و همچنان هزینه بالایی برای ایجاد اتصال دارد.

وب‌سوکت‌ها (WebSockets) این محدودیت‌ها را با معرفی یک پروتکل استاندارد برای ارتباط دوطرفه، همزمان و مداوم (full-duplex) روی یک اتصال TCP واحد حل کردند.


وب‌سوکت چیست؟

وب‌سوکت‌ها (تعریف شده در RFC 6455) در کنار HTTP کار می‌کنند. در حالی که HTTP یک پروتکل بدون حالت (stateless) است که در آن فقط کلاینت می‌تواند درخواست‌ها را آغاز کند، اتصال وب‌سوکت پس از برقرار شدن به طور نامحدود باز می‌ماند و به کلاینت و سرور اجازه می‌دهد در هر زمان با کمترین تاخیر (latency) به یکدیگر داده ارسال کنند.

قانون اساسی وب‌سوکت‌ها این است:

پس از برقراری اتصال، هر یک از طرفین می‌توانند در هر زمان و بدون نیاز به شروع درخواست اتصال جدید، پیام ارسال کنند.


راهنمای گام به گام: چرخه حیات اتصال

یک اتصال وب‌سوکت سه مرحله متمایز را طی می‌کند: دست‌تکانی (Handshake)، انتقال داده و بستن اتصال.

WebSocket Connection Lifecycle Diagram

۱. دست‌تکانی HTTP (ارتقای پروتکل)

از آنجا که دیوارهای آتش و مسیریاب‌ها برای اجازه دادن به ترافیک وب استاندارد روی پورت‌های 80 (HTTP) و 443 (HTTPS) پیکربندی شده‌اند، اتصال وب‌سوکت به عنوان یک درخواست استاندارد HTTP/1.1 آغاز می‌شود. به این فرآیند Upgrade Handshake می‌گویند.

درخواست کلاینت

کلاینت یک درخواست HTTP GET را با هدرهای مشخصی ارسال می‌کند که درخواست تغییر پروتکل را دارند:

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: یک مقدار تصادفی ۱۶ بایتی که در Base64 کدگذاری شده است. این مقدار برای اثبات اینکه سرور دست‌تکانی را دریافت کرده و پروتکل وب‌سوکت را درک می‌کند، استفاده می‌شود.
  • Sec-WebSocket-Version: نسخه پروتکل وب‌سوکت را مشخص می‌کند (معمولاً ۱۳).
  • 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 خام تبدیل می‌شود و هر دو طرف به پروتکل وب‌سوکت منتقل می‌شوند.

۲. فریم‌بندی و انتقال داده

برخلاف HTTP که هدرهای متنی ساده و به دنبال آن بدنه ارسال می‌کند، وب‌سوکت داده‌ها را در بسته‌های باینری ساختاریافته به نام فریم (frame) منتقل می‌کند.

یک فریم وب‌سوکت دارای یک هدر بسیار سبک (بین ۲ تا ۱۴ بایت) و به دنبال آن بار داده (payload) است. این هدر شامل موارد زیر است:

  • بیت FIN (یک بیت): نشان می‌دهد که آیا این فریم نهایی پیام است یا خیر.
  • Opcode (۴ بیت): نوع فریم را مشخص می‌کند:
    • 0x1: فریم متنی (کدگذاری شده با UTF-8)
    • 0x2: فریم باینری
    • 0x8: درخواست بستن اتصال
    • 0x9: پینگ (Ping)
    • 0xA: پونگ (Pong)
  • بیت ماسک (یک بیت): مشخص می‌کند که آیا داده‌های payload ماسک شده‌اند یا خیر.
  • طول Payload: اندازه داده‌ها.
  • کلید ماسک‌گذاری (۴ بایت): الزام امنیتی حیاتی: تمام فریم‌های ارسال شده از کلاینت به سرور باید با استفاده از یک کلید تصادفی ۴ بایتی ماسک شوند (با عملیات XOR مبهم شوند). این کار از خواندن ترافیک توسط پروکسی‌ها یا اجرای حملات مسمومیت حافظه کش جلوگیری می‌کند. فریم‌های سرور به کلاینت نباید ماسک شوند.

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

برای جلوگیری از بستن اتصالات غیرفعال توسط مسیریاب‌ها و متعادل‌کننده‌های بار، هر یک از طرفین می‌توانند فریم Ping ارسال کنند. طرف دریافت‌کننده باید بلافاصله با فریم Pong حاوی همان payload پاسخ دهد.

۳. بستن اتصال

برای بستن اتصال به صورت تمیز:

  1. یک طرف فریم Close حاوی یک کد وضعیت (مانند 1000 برای بستن عادی، و 1006 برای بستن غیرعادی) و یک دلیل متنی اختیاری ارسال می‌کند.
  2. طرف دیگر با فریم Close خود پاسخ می‌دهد.
  3. سوکت TCP زیرین بسته می‌شود.

مثال کد: پیاده‌سازی وب‌سوکت در Node.js

برای دیدن وب‌سوکت در عمل، بیایید یک برنامه ساده Node.js بنویسیم. ما یک سرور وب‌سوکت محلی ایجاد می‌کنیم که هر پیامی را که دریافت می‌کند بازتاب می‌دهد، به همراه یک اسکریپت کلاینت برای اتصال به آن.

سرور وب‌سوکت (server.js)

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

// ۱. ایجاد یک سرور HTTP استاندارد
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('HTTP Server running. Use WebSocket to connect.\n');
});

// ۲. اتصال سرور وب‌سوکت به سرور 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: 'به سرور وب‌سوکت غزنکس متصل شدید!' }));

    // گوش دادن به پیام‌های ورودی از این کلاینت
    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 در حال گوش دادن است');
});

کلاینت مرورگر (جاوا اسکریپت سمت کلاینت)

می‌توانید این کلاینت را مستقیماً در کنسول مرورگر خود اجرا کنید:

// ۱. ایجاد اتصال به سرور
const socket = new WebSocket('ws://localhost:8080');

// ۲. هندلر باز شدن اتصال
socket.addEventListener('open', (event) => {
    console.log('[کلاینت] متصل شد به سرور.');
    
    const payload = JSON.stringify({ text: 'سلام سرور!' });
    socket.send(payload);
    console.log(`[کلاینت] ارسال شد: ${payload}`);
});

// ۳. گوش دادن به پیام‌های سرور
socket.addEventListener('message', (event) => {
    const response = JSON.parse(event.data);
    console.log('[کلاینت] پیام دریافت شد از سرور:', response);
});

// ۴. گوش دادن به بستن اتصال
socket.addEventListener('close', (event) => {
    console.log(`[کلاینت] اتصال بسته شد (کد: ${event.code})`);
});

// ۵. گوش دادن به خطاها
socket.addEventListener('error', (error) => {
    console.error('[کلاینت] خطای وب‌سوکت:', error);
});

HTTP در مقابل وب‌سوکت: مقایسه دقیق

ویژگی HTTP/1.1 وب‌سوکت (WebSockets)
ارتباط یک‌طرفه (آغاز توسط کلاینت) دوطرفه (آغاز توسط کلاینت یا سرور)
مدل اتصال درخواست-پاسخ (کوتاه‌مدت) مداوم و طولانی‌مدت
سربار (Overhead) بالا (ارسال هدرها با هر درخواست) بسیار پایین (کمترین سربار فریم‌بندی)
حالت (State) بدون حالت با حالت (حفظ سياق اتصال)
پروتکل http:// یا https:// ws:// یا wss://
بهترین کاربرد دریافت اسناد، REST APIs چت‌های بلادرنگ، داشبوردها، بازی‌های زنده

ملاحظات امنیتی برای وب‌سوکت‌ها

از آنجا که وب‌سوکت‌ها پس از دست‌تکانی، مسیریابی استاندارد HTTP را دور می‌زنند، تهدیدات امنیتی منحصر به فردی را معرفی می‌کنند:

  1. استفاده از وب‌سوکت ایمن (wss://): همیشه وب‌سوکت‌ها را روی TLS/SSL (پورت 443) اجرا کنید. WSS بار داده فریم را رمزگذاری می‌کند و از استراق سمع و دستکاری جلوگیری می‌کند.
  2. اعتبارسنجی منبع (Origin Validation): وب‌سوکت‌ها توسط سیاست هم‌منشا (SOP) محدود نمی‌شوند. همیشه هدر Origin را در سرور در طول دست‌تکانی اعتبارسنجی کنید تا از دسترسی غیرمجاز جلوگیری شود.
  3. احراز هویت در دست‌تکانی: کاربران را قبل از برقراری اتصال احراز هویت کنید. این کار معمولاً با ارسال توکن (مانند JWT) در پارامترهای آدرس یا بررسی کوکی‌های نشست انجام می‌شود.
  4. پاکسازی ورودی‌ها (Sanitization): هر پیامی را که از طریق وب‌سوکت دریافت می‌شود به عنوان ورودی غیرقابل اعتماد در نظر بگیرید. داده‌ها را برای جلوگیری از حملات XSS پاکسازی و اعتبارسنجی کنید.

خلاصه

وب‌سوکت‌ها با حذف سربار نظرسنجی‌های سنتی HTTP، برنامه‌های وب بلادرنگ را متحول کردند. با حفظ یک اتصال TCP مداوم، انتقال پیام‌های دوطرفه فوری را ممکن می‌سازند که داشبوردهای زنده، بازی‌های چندنفره و برنامه‌های چت امروزی را پشتیبانی می‌کند. درک فرآیند ارتقای HTTP، معماری فریم‌بندی و اقدامات امنیتی حیاتی به شما کمک می‌کند خدمات بلادرنگ سریع و امنی بسازید.


آموزش‌ها و راهنماهای توسعه‌دهنده بیشتری را در وبلاگ غزنکس کاوش کنید →