مقدمة: لماذا نحتاج إلى WebSocket متقدم على Kotlin؟
\nتتطلب تطبيقات الويب الحديثة بشكل متزايد نقلًا فوريًا للبيانات: الدردشات، الإشعارات، الألعاب عبر الإنترنت، عروض الأسعار المالية ولوحات المراقبة. يتيح بروتوكول WebSocket الأساسي إنشاء اتصال ثنائي الاتجاه دائم بين العميل والخادم، ولكن في الممارسة العملية، يتطلب تطوير تطبيق وقت فعلي موثوق وقابل للتوسع على Kotlin أكثر بكثير من مجرد فتح socket.
\nفي هذه المقالة، سنتجاوز حدود خادم echo البسيط. سننظر في الأنماط المعمارية للعمل مع آلاف الاتصالات، ومعالجة الأخطاء باستخدام كوروتينات Kotlin، وتجميع الخوادم مع وسطاء الرسائل، وقضايا الأمان. المادة موجهة للمطورين الملمين بالفعل بأساسيات Kotlin و Ktor.
\n\n1. هندسة خادم WebSocket عالي التحميل على Ktor
\nبالنسبة لحل الإنتاج، لا يكفي مجرد تعليق معالج webSocket. من الضروري التفكير في إدارة الجلسات، ودورة حياة الاتصال، وعزل المعالجات. دعنا ننظر في العناصر الأساسية.
إدارة الجلسات وتجميعها
\nيتم تمثيل كل اتصال WebSocket في Ktor بواسطة كائن DefaultWebSocketSession. لإرسال الرسائل إلى مستخدم معين أو مجموعة (مثل "غرفة الدردشة")، تحتاج إلى تخزين الجلسات النشطة. استخدم ConcurrentHashMap لسلامة الخيوط.
import io.ktor.websocket.*\nimport java.util.concurrent.ConcurrentHashMap\n\nclass SessionManager {\n private val sessions = ConcurrentHashMap<String, WebSocketSession>()\n private val roomSessions = ConcurrentHashMap<String, MutableSet<String>>()\n\n fun addUserToRoom(roomId: String, userId: String, session: WebSocketSession) {\n sessions[userId] = session\n roomSessions.computeIfAbsent(roomId) { mutableSetOf() }.add(userId)\n }\n\n suspend fun broadcastToRoom(roomId: String, message: String) {\n roomSessions[roomId]?.forEach { userId ->\n sessions[userId]?.send(Frame.Text(message))\n }\n }\n\n fun removeUser(userId: String) {\n sessions.remove(userId)\n roomSessions.forEach { (_, users) -> users.remove(userId) }\n }\n}\n\nمعالجة دورة الحياة باستخدام الكوروتينات
\nيستخدم Ktor الكوروتينات لمعالجة كل اتصال. من المهم معالجة الاستثناءات وإغلاق socket بشكل صحيح لتجنب تسرب الذاكرة. استخدم try-catch داخل كتلة webSocket واستدعِ removeUser في كتلة finally.
install(WebSockets) {\n pingPeriod = Duration.ofSeconds(15)\n timeout = Duration.ofSeconds(30)\n maxFrameSize = Long.MAX_VALUE\n masking = false\n}\n\nembeddedServer(Netty, port = 8080) {\n val sessionManager = SessionManager()\n routing {\n webSocket("/chat/{roomId}") {\n val roomId = call.parameters["roomId"] ?: "general"\n val userId = call.request.queryParameters["userId"] ?: "anon"\n sessionManager.addUserToRoom(roomId, userId, this)\n try {\n for (frame in incoming) {\n if (frame is Frame.Text) {\n val text = frame.readText()\n sessionManager.broadcastToRoom(roomId, "[$userId]: $text")\n }\n }\n } catch (e: Exception) {\n log.error("WebSocket error for user $userId: ${e.localizedMessage}")\n } finally {\n sessionManager.removeUser(userId)\n log.info("User $userId disconnected from room $roomId")\n }\n }\n }\n}.start(wait = true)\n\n2. توسيع وتجميع WebSocket على Kotlin
\nلن يتمكن خادم واحد من التعامل مع عشرات الآلاف من الاتصالات المتزامنة. الحل هو التوسع الأفقي. ومع ذلك، فإن اتصال WebSocket "ي