يتطلب مستوى متوسط. محتاج تكون مرتاح مع أساسيات Node.js وسطر أوامر npm، وفكرة بسيطة عن JavaScript في المتصفح. مش محتاج أي خبرة سابقة في الشبكات.
في نهاية المقال هيكون عندك شات لحظي شغّال بالكامل: خادم Node.js يستقبل رسالة من مستخدم ويوزّعها على كل المتصلين في أقل من مللي ثانية، بأقل من 60 سطر كود، من غير ما المتصفح يسأل السيرفر كل شوية.
اعمل شات لحظي بـ WebSocket في Node.js
المشكلة باختصار
الطريقة الشائعة عشان المتصفح يعرف إن في رسالة جديدة هي إنه يسأل السيرفر كل شوية (polling). الطريقة دي بتفشل في اللحظية والتكلفة مع أول ضغط حقيقي.
ركز في الأرقام. لو عندك 10 آلاف مستخدم والمتصفح بيسأل كل ثانيتين، ده 300 ألف طلب في الدقيقة، أغلبها بيرجع "مفيش جديد". وكل طلب بيحمل هيدرز HTTP حجمها 700 إلى 800 بايت. وفوق ده، الرسالة ممكن تتأخر لحد الطلب الجاي، يعني تأخير يوصل 2000 مللي ثانية بالظبط قد الفاصل بين طلبين.
مثال يقرّب الفكرة
تخيّل الفرق بين مكالمة تليفون وتبادل خطابات بالبريد. مع الخطابات، عشان تعرف في رد، لازم تبعت خطاب كل شوية تسأل: وصل جديد؟ ده الـ polling، مجهود ضائع في أغلب الأحيان. مع المكالمة، الخط مفتوح، والطرفين يتكلموا في أي لحظة من غير ما حد يعيد الاتصال. ده بالظبط اللي بيعمله WebSocket.
بالتفاصيل وبدقة: WebSocket بروتوكول اتصال ثنائي الاتجاه (full-duplex) فوق اتصال TCP واحد دائم. بيبدأ بطلب HTTP عادي فيه هيدر Upgrade: websocket، والسيرفر يرد بـ 101 Switching Protocols، وبعدها القناة تفضل مفتوحة والطرفين يبعتوا في أي وقت. المعيار موصوف رسميًا في RFC 6455.
الخطوة 1: ثبّت المكتبة وابنِ الخادم
هنستخدم مكتبة ws، أشهر مكتبة WebSocket في Node.js واللي مبني عليها مكتبات أعلى زي Socket.IO. ابدأ بتثبيتها:
npm init -y
npm install wsبعدين اعمل ملف server.js:
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
console.log('اتصال جديد. العدد الحالي:', wss.clients.size);
socket.on('message', (data) => {
// بث الرسالة لكل المتصلين
for (const client of wss.clients) {
if (client.readyState === client.OPEN) {
client.send(data.toString());
}
}
});
});
console.log('شغّال على ws://localhost:8080');الخطوة 2: وصّل المتصفح
الكلاينت في المتصفح مش محتاج أي مكتبة. WebSocket جزء من المتصفح نفسه. اعمل ملف index.html:
<script>
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => ws.send('مرحبا، أنا هنا');
ws.onmessage = (e) => console.log('وصلت رسالة:', e.data);
</script>افتح الملف في تبويبين. أي رسالة تبعتها من تبويب هتظهر في التبويبين في نفس اللحظة. ده البث شغّال.
الخطوة 3: البث لكل المتصلين
الحلقة اللي فوق على wss.clients هي قلب الشات. السيرفر بيستقبل رسالة من مرسِل واحد، ويوزّعها على كل من فتح اتصال حالته OPEN. رسالة واحدة داخلة، تطلع لكل المتصلين، دفعة واحدة، بلا إعادة اتصال ولا طلبات جديدة.
الخطوة 4: اكتشف الاتصالات الميتة بنبض
مشكلة حقيقية بتحصل فعلاً: لو مستخدم قفل النت فجأة، السيرفر ممكن يفضل فاكر إنه متصل ويحتفظ باتصال ميت يستهلك ذاكرة. الحل نبض (heartbeat) كل 30 ثانية:
function heartbeat() { this.isAlive = true; }
wss.on('connection', (socket) => {
socket.isAlive = true;
socket.on('pong', heartbeat);
});
setInterval(() => {
for (const client of wss.clients) {
if (client.isAlive === false) { client.terminate(); continue; }
client.isAlive = false;
client.ping();
}
}, 30000);التحقق من أنه يعمل
- شغّل
node server.js، لازم تشوف رسالة "شغّال على ws://localhost:8080". - افتح
index.htmlفي تبويبين مختلفين. - في كونسول أي تبويب اكتب
ws.send('اختبار'). لازم تشوف "وصلت رسالة: اختبار" في التبويبين معًا. - اقفل تبويب وراقب لوج السيرفر: العدد الحالي لازم ينقص خلال 30 ثانية بفضل النبض.
الـ trade-offs وما يجب الانتباه له
WebSocket بيكسب في اللحظية والكفاءة: الاتصال بيتفتح مرة واحدة، وأوفرهيد الإطار الواحد ممكن يكون 2 بايت بس، مقابل مئات البايتات في كل طلب polling. المكسب: تأخير بالمللي ثانية بدل ثواني.
بتخسر إيه؟ الاتصالات دي stateful، وده معناه:
- التوسّع الأفقي أصعب. الافتراض في الكود ده إنك على نسخة Node واحدة. لو شغّلت أكتر من نسخة خلف موازِن حمل، كل نسخة بتشوف عملاءها بس. عشان تبث للجميع عبر النسخ، محتاج طبقة نشر/اشتراك زي Redis Pub/Sub.
- مفيش إعادة اتصال تلقائية. لازم تكتبها بنفسك في الكلاينت.
- البنية التحتية لازم تدعم الترقية. بعض موازنات الحمل والبروكسيات محتاجة إعداد صريح عشان تمرّر هيدر
Upgradeوتسيب الاتصال مفتوح مدة طويلة.
متى لا تستخدم WebSocket
- لو التحديثات في اتجاه واحد بس (من السيرفر للمتصفح)، زي إشعارات أو سعر متغير، الأبسط هو Server-Sent Events، وبيشتغل فوق HTTP عادي بلا تعقيد.
- لو التحديثات نادرة (كل دقيقة أو أكتر)، الـ polling البسيط أرخص وأسهل في الصيانة.
- لو محتاج دلالات طلب/رد صارمة وتخزين مؤقت زي REST، خلي HTTP في مكانه.
الخطوة التالية
خد الكود اللي فوق وضيف إعادة اتصال في الكلاينت: لُفّ إنشاء new WebSocket(...) في دالة، ونادِها من جديد داخل ws.onclose بعد ثانيتين. جرّب تقفل السيرفر وتشغّله تاني، ولاحظ الكلاينت بيرجع يتصل لوحده. لو اتصل، إنت جاهز تبني عليه شات حقيقي بأسماء مستخدمين وغرف.
المصادر
- MDN Web Docs — WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
- RFC 6455 — The WebSocket Protocol: https://datatracker.ietf.org/doc/html/rfc6455
- مكتبة ws — التوثيق الرسمي: https://github.com/websockets/ws
- Node.js — التوثيق الرسمي: https://nodejs.org/en/docs
- MDN Web Docs — Server-Sent Events: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events