WebSocket на Kotlin: создание real-time приложений с нуля
Современные веб-приложения всё чаще требуют мгновенной передачи данных между клиентом и сервером. Традиционный HTTP, работающий по принципу «запрос-ответ», не подходит для задач, где нужна постоянная двусторонняя связь — чаты, онлайн-игры, биржевые котировки или уведомления в реальном времени. Именно здесь на помощь приходит протокол WebSocket. В этой статье мы разберём, как создавать real-time приложения на Kotlin с использованием WebSocket, от базовой теории до практических примеров.
Kotlin, благодаря своей лаконичности, безопасности и полной совместимости с Java-экосистемой, является отличным выбором для разработки серверной части. В качестве основного фреймворка мы будем использовать Ktor — современный асинхронный фреймворк для Kotlin, который имеет встроенную поддержку WebSocket.
Что такое WebSocket и зачем он нужен?
WebSocket — это протокол, обеспечивающий полнодуплексную (двустороннюю) связь между клиентом и сервером через одно постоянное TCP-соединение. В отличие от HTTP, где инициатором запроса всегда выступает клиент, WebSocket позволяет серверу самостоятельно отправлять данные клиенту в любой момент.
Основные преимущества WebSocket:
- Низкая задержка: Нет накладных расходов на установку нового соединения для каждого сообщения.
- Эффективность: Минимальный служебный трафик (всего 2 байта на сообщение после установки соединения).
- Постоянное соединение: Сервер может отправлять данные в реальном времени без опроса со стороны клиента.
WebSocket идеально подходит для:
- Онлайн-чатов и мессенджеров
- Совместного редактирования документов
- Биржевых и финансовых котировок
- Многопользовательских игр
- Push-уведомлений и мониторинга
Настройка проекта Ktor для WebSocket
Прежде чем писать код, необходимо настроить проект. Проще всего это сделать через IntelliJ IDEA с плагином Ktor, или вручную через Gradle.
Шаг 1: Создание проекта
Создайте новый Gradle-проект с Kotlin (JVM). В файл build.gradle.kts добавьте следующие зависимости:
plugins {
kotlin("jvm") version "1.9.22"
id("io.ktor.plugin") version "2.3.7"
}
application {
mainClass.set("ApplicationKt")
}
dependencies {
implementation("io.ktor:ktor-server-core:2.3.7")
implementation("io.ktor:ktor-server-netty:2.3.7")
implementation("io.ktor:ktor-server-websockets:2.3.7")
implementation("ch.qos.logback:logback-classic:1.4.14")
}
Шаг 2: Базовая структура сервера
Создадим файл Application.kt и напишем минимальный WebSocket-сервер:
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.delay
fun main() {
embeddedServer(Netty, port = 8080) {
install(WebSockets)
webSocket("/chat") {
send("Добро пожаловать в чат!")
for (frame in incoming) {
if (frame is Frame.Text) {
val text = frame.readText()
send("Вы написали: $text")
println("Получено: $text")
}
}
}
}.start(wait = true)
}
Этот код запускает сервер на порту 8080, который принимает WebSocket-соединения по адресу /chat. При подключении клиент получает приветственное сообщение, а затем сервер отвечает на каждое текстовое сообщение эхом.
Создание продвинутого чата с комнатами
Теперь перейдём к более сложному примеру — многопользовательскому чату с поддержкой комнат. Мы реализуем функционал, где пользователи могут подключаться к разным комнатам и общаться внутри них.
Архитектура чата
Нам понадобится:
- Коллекция комнат: Хранит список активных комнат и их участников.
- Обработка подключений: Присоединение и выход из комнаты.
- Расслылка сообщений: Отправка сообщений всем участникам комнаты.
Реализация сервера
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class Room(val name: String, val clients: MutableSet<WebSocketSession> = mutableSetOf())
class ChatServer {
private val rooms = mutableMapOf<String, Room>()
fun joinRoom(roomName: String, session: WebSocketSession) {
val room = rooms.getOrPut(roomName) { Room(roomName) }
room.clients.add(session)
broadcast(roomName, "Пользователь присоединился к комнате $roomName")
}
fun leaveRoom(roomName: String, session: WebSocketSession) {
val room = rooms[roomName] ?: return
room.clients.remove(session)
broadcast(roomName, "Пользователь покинул комнату $roomName")
if (room.clients.isEmpty()) {
rooms.remove(roomName)
}
}
suspend fun broadcast(roomName: String, message: String) {
val room = rooms[roomName] ?: return
room.clients.forEach { client ->
try {
client.send(Frame.Text(message))
} catch (e: Exception) {
room.clients.remove(client)
}
}
}
}
fun main() {
val chatServer = ChatServer()
embeddedServer(Netty, port = 8080) {
install(WebSockets)
webSocket("/chat/{roomName}") {
val roomName = call.parameters["roomName"] ?: "general"
chatServer.joinRoom(roomName, this)
try {
for (frame in incoming) {
if (frame is Frame.Text) {
val text = frame.readText()
chat