Introdução: Por que um WebSocket avançado em Kotlin?
\nAs aplicações web modernas exigem cada vez mais transmissão instantânea de dados: chats, notificações, jogos online, cotações financeiras e painéis de monitoramento. O protocolo WebSocket básico permite estabelecer uma conexão bidirecional permanente entre cliente e servidor, mas na prática, desenvolver uma aplicação real-time confiável e escalável em Kotlin exige muito mais do que simplesmente abrir um socket.
\nNeste artigo, iremos além de um simples servidor echo. Abordaremos padrões arquiteturais para lidar com milhares de conexões, tratamento de erros usando corrotinas Kotlin, clusterização de servidores com brokers de mensagens e questões de segurança. O material é voltado para desenvolvedores já familiarizados com os fundamentos de Kotlin e Ktor.
\n\n1. Arquitetura de um servidor WebSocket de alta carga em Ktor
\nPara uma solução de produção, não basta apenas adicionar um handler webSocket. É necessário planejar o gerenciamento de sessões, o ciclo de vida da conexão e o isolamento dos handlers. Vamos analisar os elementos-chave.
Gerenciamento de sessões e seu agrupamento
\nCada conexão WebSocket no Ktor é representada por um objeto DefaultWebSocketSession. Para enviar mensagens a um usuário específico ou a um grupo (por exemplo, "sala de chat"), é necessário armazenar as sessões ativas. Use ConcurrentHashMap para segurança em threads.
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\nTratamento do ciclo de vida com corrotinas
\nO Ktor usa corrotinas para processar cada conexão. É importante tratar corretamente as exceções e o fechamento do socket para evitar vazamentos de memória. Use try-catch dentro do bloco webSocket e chame removeUser no bloco 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("Erro WebSocket para o usuário $userId: ${e.localizedMessage}")\n } finally {\n sessionManager.removeUser(userId)\n log.info("Usuário $userId desconectado da sala $roomId")\n }\n }\n }\n}.start(wait = true)\n\n2. Escalabilidade e clusterização de WebSocket em Kotlin
\nUm único servidor não conseguirá lidar com dezenas de milhares de conexões simultâneas. A solução é a escalabilidade horizontal. No entanto, a conexão WebSocket "p