العمل المتقدم مع WebSocket في JavaScript: من النموذج الأولي إلى الإنتاج
\nWebSocket هو بروتوكول يوفر اتصالاً ثنائي الاتجاه بالكامل بين العميل والخادم عبر اتصال TCP واحد دائم. في المقالات السابقة، تناولنا الأساسيات: إنشاء الاتصال، إرسال الرسائل، والدردشات البسيطة. ومع ذلك، لإنشاء تطبيقات الوقت الفعلي الموثوقة والقابلة للتوسع حقًا (الألعاب عبر الإنترنت، منصات التداول، أنظمة التحرير التعاوني)، من الضروري إتقان التقنيات المتقدمة. في هذه المادة، سنناقش الهندسة المعمارية لخادم WebSocket من فئة الإنتاج على Node.js، بما في ذلك التوسع، إدارة الحالة، التفويض، ومعالجة الأخطاء.
\n\n1. الهندسة المعمارية المتقدمة للخادم وإدارة الغرف
\nأبسط خادم WebSocket (يعتمد على مكتبة ws) يعالج جميع العملاء في كائن عام واحد. في المشاريع الحقيقية، نحتاج إلى عزل منطقي - غرف (rooms) وقنوات (channels).
لننظر إلى مثال على الهندسة المعمارية باستخدام فئة RoomManager:
const WebSocket = require('ws');\n\nclass RoomManager {\n constructor() {\n this.rooms = new Map(); // roomName -> Set<WebSocket>\n }\n\n join(ws, roomName) {\n if (!this.rooms.has(roomName)) {\n this.rooms.set(roomName, new Set());\n }\n this.rooms.get(roomName).add(ws);\n ws.room = roomName; // حفظ الغرفة على المقبس\n }\n\n leave(ws) {\n if (ws.room && this.rooms.has(ws.room)) {\n this.rooms.get(ws.room).delete(ws);\n if (this.rooms.get(ws.room).size === 0) {\n this.rooms.delete(ws.room);\n }\n }\n }\n\n broadcast(roomName, message, excludeWs = null) {\n if (!this.rooms.has(roomName)) return;\n this.rooms.get(roomName).forEach(client => {\n if (client !== excludeWs && client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify(message));\n }\n });\n }\n}\n\n// الاستخدام\nconst wss = new WebSocket.Server({ port: 8080 });\nconst roomManager = new RoomManager();\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => {\n const msg = JSON.parse(data);\n switch (msg.type) {\n case 'join':\n roomManager.join(ws, msg.room);\n break;\n case 'message':\n roomManager.broadcast(ws.room, { user: msg.user, text: msg.text }, ws);\n break;\n }\n });\n\n ws.on('close', () => roomManager.leave(ws));\n});\nيسمح هذا الكود بإنشاء دردشات معزولة أو جلسات ألعاب. لاحظ معالجة إغلاق الاتصال - هذا أمر بالغ الأهمية لمنع تسرب الذاكرة.
\n\n2. التوسع: التجميع و Redis Pub/Sub
\nلا يمكن لعملية Node.js واحدة استخدام أكثر من نواة CPU واحدة بكفاءة. للتوسع، نستخدم الوحدة المدمجة cluster. ومع ذلك، تنشأ مشكلة: اتصالات WebSocket مرتبطة بعامل معين. كيف نرسل رسالة إلى مستخدم متصل بعامل آخر؟
الحل هو وسيط رسائل خارجي، مثل Redis Pub/Sub.
\nconst cluster = require('cluster');\nconst http = require('http');\nconst { Server } = require('ws');\nconst redis = require('redis');\n\nconst REDIS_CHANNEL = 'ws:messages';\n\nif (cluster.isMaster) {\n // تشغيل العمال حسب عدد النوى\n const numCPUs = require('os').cpus().length;\n for (let i = 0; i < numCPUs; i++) {\n cluster.fork();\n }\n\n cluster.on('exit', (worker) => {\n console.log(`Worker ${worker.process.pid} died`);\n cluster.fork(); // إعادة التشغيل\n });\n} else {\n // العامل\n const server = http.createServer();\n const wss = new Server({ server });\n\n // الاتصال بـ Redis\n const pub = redis.createClient();\n const sub