WebSocket به زبانی ساده

ترجمه و تالیف : ارسطو عباسی
تاریخ انتشار : 28 اسفند 98
خواندن در 4 دقیقه
دسته بندی ها : برنامه نویسی

WebSocket به کاربران اجازه می‌دهد تا بتوانند پیام‌هایی را بین خودشان و سرور رد و بدل کنند. از این رو وب‌سوکت یک راهکار برای ارتباط برقرار کردن میان کلاینت و سرور است. ابتدای کار بیایید برای اینکه مقاله را به ساده‌ترین شکل ممکن پیش ببریم، منظورمان را به صورت دقیق از واژه ارتباط برقرار کردن بیان کنیم، بعد از آن به بحث وب‌سوکت برخواهیم گشت.

کلاینت - سرور - ارتباط برقرار کردن

مرورگر شما که ما آن را به عنوان کلاینت می‌شناسیم، با سرور، از طریق پروتکل TCP/IP ارتباط برقرار می‌کند. در این فرایند HTTP یا Hypertext Transfer Protocol که یک پروتکل اپلیکیشن استاندارد بوده جزوی از TCP/IP به حساب می‌آید، مدیریت درخواست و جواب را در اختیار می‌گیرد. اما این اتفاقات چگونه می‌افتند؟

۱- ابتدای کار مرورگر یا کلاینت یک درخواست را به سرور ارسال می‌کند.

۲- سپس یک ارتباط ایجاد خواهد شد.

۳- بعد از آن سرور جوابی ارسال می‌کند.

۴- حال کلاینت جواب سرور را دریافت کرده و در نهایت...

۵- ارتباط بسته می‌شود.

این ساده‌ترین شکلی بود که می‌توان برای شیوه ارتباط برقرار کردن میان کلاینت و سرور در نظر گرفت.

مرحله پنجم که مرحله نهایی این ارتباط است زمانی اتفاق می‌افتد که درخواست‌های مربوط به HTTP دیگر مورد نیاز نبوده و در نهایت ارتباط بسته می‌شود.

اگر سرور بخواهد پیامی به کلاینت ارسال کند چه؟

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

اما کلاینت از کجا می‌داند که سرور قصد ارسال پیغامی را دارد؟

برای درک این موضوع به مثال زیر توجه کنید:

کلاینت غذایی را سفارش داده و منتظر است که آن غذا برای‌ش آماده و ارسال شود. در این پروسه کلاینت هر یک ثانیه، یک بار، برای بررسی آماده شدن غذا، پیغامی ارسال می‌کند:

کلاینت - ثانیه صفر: غذا آماده است؟

سرور - ثانیه صفر: نه،‌ منتظر بمانید.

کلاینت - ثانیه اول: غذا آماده است؟

سرور - ثانیه اول: نه،‌ منتظر بمانید.

کلاینت - ثانیه دوم: غذا آماده است؟

سرور - ثانیه دوم: نه،‌ منتظر بمانید.

کلاینت - ثانیه سوم: غذا آماده است؟

سرور - ثانیه سوم: بله، سفارش شما آماده است.

به این حالت HTTP Polling گفته می‌شود و همانطور که مشاهده می‌کنید روشی چندان بهینه و مناسب برای  کار ما نیست چرا که منابع بسیار زیادی را صرف کرده و تعداد درخواست‌های شکست خورده بسیار زیادی دارد.

راهی برای حل این مشکل وجود دارد؟

تکنیک‌های polling مختلفی وجود دارد که به شما در حل این مشکل می‌تواند کمک کند. یکی از این روش‌ها Long-Polling نام دارد.

در روش Long-Polling بعد از ایجاد یک درخواست HTTP، ارتباط را تا زمانی که سرور پاسخگو باشد نگه می‌دارند. این زمان توسط سرور تعیین می‌شود. اگر بخواهیم این تکنیک را روی مثال قبلی اعمال کنیم خروجی به صورت زیر خواهد بود:

کلاینت - ثانیه صفر: غذا آماده است؟

سرور - ثانیه سوم: بله، سفارش شما آماده است.

مشکل حل شد؟ از نظر تکنیکی بله، اما این راهکار نیز مشکلات خود را دارد. برای مثال تکنیک Long-Polling استفاده بسیار زیادی از پردازنده، حافظه و پهنای باند کرده و به همین دلیل باز هم روش بهینه‌ای نیست. دلیل این مصرف زیاد نیز نگه‌داشتن منابع برای ارتباطی است که هنوز بسته نشده است. اما با وجود این تکنیک، آیا راهکار دیگه‌ای باقی می‌ماند؟ پاسخ: WebSocket.

چرا وب‌سوکت؟

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

در این روش شما منتظر پیام سرور خواهید ماند، در نهایت سرور اگر پیامی داشته باشد برای شما ارسال می‌کند.

این موضوع نشان از بهینه بودن وب‌سوکت برای مسئله کارایی‌ست.

مصرف منابع

در چارت زیر می‌توانید شاهد مصرف پهنای باند در حالت Polling و Web Socket برای نمونه‌های عادی باشید. 

WebSocket به زبانی ساده

همانطور که مشاهده می‌کنید تفاوت‌ها بسیار زیاد و فاحش هستند.

سرعت

در چارت بعدی می‌توانید شاهد سرعت اجرا شدن درخواست‌ها در واحد زمانی ثانیه باشید. 

WebSocket به زبانی ساده

همانطور که مشاهده می‌کنید، ایجاد یک درخواست تنها برای هر ارتباط تقریبا ۵۰ درصد کُندتر از HTTP است. این موضوع بدان دلیل است که ایجاد یک ارتباط اولیه در Web Socket به زمان نیاز دارد. اما زمانی که این مقدار به ۵۰ درخواست رسید می‌توان مشاهده کرد که سرعت اجرا دو برابر شده است. برای آنکه دقیق‌تر این موضوع را متوجه شوید ما در یک آزمایش دیگر تعداد درخواست‌ها را از ۱، ۱۰، ۵۰ به ۵۰۰، ۱۰۰۰، ۲۰۰۰ می‌رسانیم تا درک بهتری خروجی را ببینید:

WebSocket به زبانی ساده

 همانطور که مشاهده می‌کنید تعداد درخواست‌های انجام شده با استفاده از وب‌سوکت در یک ثانیه برابر با ۳۹۰۰ مورد بوده در حالیکه HTTP برابر با ۹۵۰ مورد است.

WebSocket چگونه کار می‌کند؟

مراحلی که در ایجاد یک وب سوکت دخیل هستند را می‌توانید در ادامه مشاهده بکنید:

۱- کلاینت یا مرورگر یک درخواست HTTP را به سرور ارسال می‌کند.

۲- ارتباط با استفاده از پروتکل HTTP ایجاد می‌شود. 

۳- اگر سرور از پروتکل وب‌سوکت پشتیبانی کند، با ارتقای حالت ارتباط موافقت می‌کند. این حالت handshake نامیده می‌شود.

۴- حال که handshake اتفاق افتاد، ارتباط اولیه HTTP با یک ارتباط وب‌سوکت جایگزین می‌شود. البته این موضوع را در نظر داشته باشید که پروتکل اصلی در هر حالت TCP/IP است.

۵- بعد از این اتفاق، داده‌ها به سادگی می‌توانند بین کلاینت و سرور ارسال شوند.

مرحله کدنویسی

برای پیاده‌سازی این موارد نیاز است تا دو فایل جدید را ایجاد کنیم: یک فایل روی سرور و یک فایل روی کلاینت.

ابتدا بیایید با سمت کلاینت شروع کنیم. برای اینکار یک فایل html جدید را با نام client.html ایجاد کرده و بعد کدهای زیر را در آن قرار دهید:

<html>

<script>
    // Our code goes here
</script>

<body>
    <h1>This is a client page</h1>
</body>

</html>

حال یک فایل دیگر ایجاد کرده و آن را server.js نامگذاری کنید. حال ماژول http را import کرده و یک server جدید را ایجاد نمایید. 

//importing http module
const http = require('http');

//creating a http server
const server = http.createServer((req, res) => {
    res.end("I am connected");
});

//making it listen to port 8000
server.listen(8000);

به یاد داشته باشید که ما برای انجام این موارد نیاز داریم تا از نودجی‌اس استفاده کنیم.

مراحل اولیه نصب کلاینت و سرور ما تمام شد حال می‌توانیم به موارد دیگر بپردازیم.

نصب کلاینت

برای ایجاد یک وب‌سوکت، از سازنده WebSocket() استفاده کنید. این سازنده یک شئ websocket را برگشت می‌دهد. با استفاده از این شئ می‌توانید به API دسترسی داشته باشید که به شما قابلیت ایجاد و مدیریت ارتباط وب‌سوکت با سرور را می‌دهد. به صورت ساده‌تر این شئ وب‌سوکت به ما کمک می‌کند تا بتوانیم یک جریان داده‌ای دو طرفه بین سرور و کلاینت را ایجاد کنیم. 

<html>

<script>
    //calling the constructor which gives us the websocket object: ws
    let ws = new WebSocket('url'); 
</script>

<body>
    <h1>This is a client page</h1>
</body>

</html>

سازنده WebSocket ما نیاز دارد که از طریق یک URL اجرا شود که البته ما آن را قبلا تعریف کرده‌ایم. می‌توانید از طریق آدرس ws://localhost:8000 به آن دسترسی داشته باشیم. البته ممکن است این آدرس با حالتی که شما در حال اجرا هستید تفاوت‌هایی داشته باشد. از آنجایی که ما از پروتکل وب‌سوکت استفاده می‌کنیم ابتدای آدرس ws قرار می‌گیرد.

نصب سرور

برای نصب وب‌سوکت روی سرور ما به یک ماژول ws نیاز خواهیم داشت. برای اینکار ابتدا نیاز است تا ماژول ws را وارد کدها کرده و سپس ما یک سرور وب‌سوکت را ایجاد می‌کنیم. این سرور نیز روی پورت ۸۰۰۰ اجرا خواهد شد. برای اینکار فایل server.js را باز کرده و براساس کدهای زیر آن را بروزرسانی کنید:

const http = require('http');
//importing ws module
const websocket = require('ws');

const server = http.createServer((req, res) => {
    res.end("I am connected");
});
//creating websocket server
const wss = new websocket.Server({ server });

server.listen(8000);

در اینجا می‌توانیم از متدهای مربوط به شئ wss استفاده کنیم تا بتوانیم رویدادهای مورد نظر را پیاده‌سازی کنیم. برای مثال به رویداد on در کدهای زیر دقت کنید:

const http = require('http');
const websocket = require('ws');

const server = http.createServer((req, res) => {
    res.end("I am connected");
});
const wss = new websocket.Server({ server });
//calling a method 'on' which is available on websocket object
wss.on('headers', (headers, req) => {
    //logging the header
    console.log(headers);
});

server.listen(8000);

این متد دو آرگومان دریافت می‌کند: نام رویداد و تابع Callback. نام رویداد برای تشخیص اینکه کدام رویداد در حال اتفاق افتادن است استفاده می‌شود و مشخصات تابع Callback نیز برای اینکه مشخص شود چه کاری باید انجام بدهیم. خروجی این قطعه کد به صورت زیر است:

 اولین چیزی که باید به آن توجه کنید کد وضعیتی است که دریافت کرده‌ایم. در اینجا می‌توانید مشاهده کنید که کد ۱۰۱ را دریافت کرده‌ایم. براساس موقعیت‌های مختلف ممکن است کدهای دیگری مانند ۲۰۰، ۲۰۱ یا ۴۰۴ را مشاهده کنید. ۱۰۱ زمانی اتفاق می‌افتد که پروتکل سوئیچ اتفاق افتاده است.

WebSocket به زبانی ساده

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

این دقیقا اتفاقی است که در فرایند handshake اتفاق خواهد افتاد.

حال می‌توانیم حتی رویدادهای Connection بیشتری را نیز اضافه کنیم. برای اینکار به صورت زیر عمل کنید:

const http = require('http');
const websocket = require('ws');

const server = http.createServer((req, res) => {
    res.end("I am connected");
});
const wss = new websocket.Server({ server });

wss.on('headers', (headers, req) => {
    //console.log(headers); Not logging the header anymore
});

//Event: 'connection'
wss.on('connection', (ws, req) => {
    ws.send('This is a message from server, connection is established');
    //receive the message from client on Event: 'message'
    ws.on('message', (msg) => {
        console.log(msg);
    });
});

server.listen(8000);

حال می‌توانیم در صفحه کلاینت به صورت زیر از رویداد مربوطه استفاده نماییم:

<html>

<script>
    let ws = new WebSocket('url'); 
    //logging the websocket property properties
    console.log(ws);
    //sending a message when connection opens
    ws.onopen = (event) => ws.send("This is a message from client");
    //receiving the message from server
    ws.onmessage = (message) => console.log(message);
</script>

<body>
    <h1>This is a client page</h1>
</body>

</html>

حال اگر به خروجی این کدها در مرورگر نگاهی بیاندازید متوجه همه چیز خواهید شد.

در پایان

در این مطلب از وبسایت راکت ما به صورت بسیار ساده قابلیت وب‌سوکت را معرفی کردیم و دلیل استفاده از آن را نیز بررسی نمودیم. بعد از آن چند بنچمارک را بررسی کرده و در نهایت آموزش عملی برای پیاده‌سازی این قابلیت را ارائه کردیم.

منبع

گردآوری و تالیف ارسطو عباسی

من ارسطو‌ام :) کافی نیست؟! :)