Einleitung: Warum fortgeschrittenes WebSocket auf Kotlin?
\nModerne Webanwendungen erfordern zunehmend eine sofortige Datenübertragung: Chats, Benachrichtigungen, Online-Spiele, Finanzkurse und Dashboards. Das grundlegende WebSocket-Protokoll ermöglicht die Einrichtung einer permanenten bidirektionalen Verbindung zwischen Client und Server, aber in der Praxis erfordert die Entwicklung einer zuverlässigen und skalierbaren Echtzeit-Anwendung auf Kotlin weit mehr als nur das Öffnen eines Sockets.
\nIn diesem Artikel gehen wir über einen einfachen Echo-Server hinaus. Wir betrachten Architekturmuster für die Arbeit mit Tausenden von Verbindungen, Fehlerbehandlung mit Kotlin-Koroutinen, Server-Clusterbildung mit Message-Brokern und Sicherheitsaspekte. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen von Kotlin und Ktor vertraut sind.
\n\n1. Architektur eines hochlastigen WebSocket-Servers mit Ktor
\nFür eine Produktionslösung reicht es nicht aus, einfach einen webSocket-Handler zu installieren. Es müssen die Sitzungsverwaltung, der Lebenszyklus der Verbindung und die Isolierung der Handler durchdacht werden. Betrachten wir die Schlüsselelemente.
Sitzungsverwaltung und deren Gruppierung
\nJede WebSocket-Verbindung in Ktor wird durch ein Objekt DefaultWebSocketSession dargestellt. Zum Senden von Nachrichten an einen bestimmten Benutzer oder eine Gruppe (z. B. "Chatraum") müssen aktive Sitzungen gespeichert werden. Verwenden Sie ConcurrentHashMap für die Threadsicherheit.
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\nLebenszyklusverwaltung mit Koroutinen
\nKtor verwendet Koroutinen zur Verarbeitung jeder Verbindung. Es ist wichtig, Ausnahmen und das Schließen des Sockets korrekt zu behandeln, um Speicherlecks zu vermeiden. Verwenden Sie try-catch innerhalb des webSocket-Blocks und rufen Sie removeUser im finally-Block auf.
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-Fehler für Benutzer $userId: ${e.localizedMessage}")\n } finally {\n sessionManager.removeUser(userId)\n log.info("Benutzer $userId hat den Raum $roomId verlassen")\n }\n }\n }\n}.start(wait = true)\n\n2. Skalierung und Clusterbildung von WebSocket auf Kotlin
\nEin einzelner Server wird mit Zehntausenden gleichzeitigen Verbindungen nicht fertig. Die Lösung ist horizontale Skalierung. Allerdings "lebt" die WebSocket-Verbindung