Long Polling vs WebSocket в Kotlin: что выбрать для реального времени
Представьте: вы пишете чат на Kotlin, биржевой терминал или систему мониторинга серверов. Клиенту нужно видеть изменения мгновенно. Традиционный HTTP-запрос «сходил-ответил» тут не подойдёт — данные должны приходить сами, без постоянных пинков от пользователя.
В мире Kotlin есть два популярных подхода: Long Polling и WebSocket. Оба позволяют организовать real-time взаимодействие, но делают это принципиально по-разному. Какой выбрать? Давайте разберёмся на живых примерах, без воды и академических лекций.
Что такое Long Polling (и почему он не умер)
Long Polling — это техника, при которой клиент отправляет HTTP-запрос, а сервер не отвечает сразу, а «висит» открытым до появления новых данных. Как только данные появляются — сервер отправляет ответ, клиент обрабатывает его и тут же открывает новый запрос. Цикл повторяется.
Выглядит как обычный HTTP, но с длинным таймаутом. Никаких дополнительных протоколов, никаких handshake’ов — всё работает поверх стандартного HTTP/1.1 или HTTP/2.
Пример Long Polling на Kotlin (Ktor Server + OkHttp Client)
Серверная часть на Ktor (сервер будет ждать, пока не появится сообщение):
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.http.*
import kotlinx.coroutines.*
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/long-poll") {
// Имитация ожидания нового сообщения
val message = withContext(Dispatchers.Default) {
delay(5000) // ждём 5 секунд (или новое событие)
"Новое сообщение: ${System.currentTimeMillis()}"
}
call.respondText(message, ContentType.Text.Plain)
}
}
}.start(wait = true)
}
Клиент на OkHttp:
import okhttp3.*
val client = OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS) // важный момент!
.build()
fun startLongPolling() {
val request = Request.Builder()
.url("http://localhost:8080/long-poll")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
// Обязательно перезапускаем polling при ошибке
startLongPolling()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (response.isSuccessful) {
println("Получено: ${response.body?.string()}")
// Сразу запускаем следующий запрос
startLongPolling()
}
}
}
})
}
Плюсы Long Polling:
- Работает везде, даже за корпоративными прокси и в старых браузерах
- Простая отладка — вы видите обычные HTTP-запросы в логах
- Легко встраивается в существующую инфраструктуру (балансировщики, кеши, CDN)
Минусы:
- Высокая задержка — между отправкой запроса и получением ответа может пройти до RTT (Round Trip Time)
- Сервер держит много открытых соединений (каждый запрос — отдельный поток/корутина)
- При переподключении возможна потеря сообщений (если не реализовать идемпотентность)
WebSocket: полноценный дуплекс
WebSocket — это отдельный протокол (ws://), который устанавливает постоянное двунаправленное соединение после HTTP-рукопожатия. Сервер и клиент могут отправлять сообщения в любой момент, без оверхеда на заголовки.
Пример WebSocket на Kotlin (Ktor Server + Ktor Client)
Сервер:
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.delay
fun main() {
embeddedServer(Netty, port = 8081) {
install(WebSockets) // включаем поддержку WebSocket
routing {
webSocket("/chat") {
send("Добро пожаловать в чат!")
for (frame in incoming) {
if (frame is Frame.Text) {
val text = frame.readText()
println("Получено: $text")
send("Эхо: $text")
}
}
}
}
}.start(wait = true)
}
Клиент на Ktor Client:
import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
suspend fun main() {
val client = HttpClient {
install(WebSockets)
}
client.webSocket("ws://localhost:8081/chat") {
send(Frame.Text("Привет, сервер!"))
for (frame in incoming) {
if (frame is Frame.Text) {
println("Ответ: ${frame.readText()}")
}
}
}
}
Плюсы WebSocket:
- Минимальная задержка — данные летят мгновенно, без повторных handshake
- Эффективное использование сети — нет заголовков HTTP на каждое сообщение
- Поддержка фреймов (бинарные данные, текст, пинг/понг)
Минусы:
- Не все прокси и фаерволы любят WebSocket (могут рвать соединение)
- Сложнее с балансировкой — нужно sticky sessions или отдельный WebSocket gateway
- Требуется явная обработка переподключения и heartbeat’ов
Когда что выбирать? Практические сценарии
Давайте честно: идеального решения нет. Всё зависит от конкретной задачи. Вот несколько типовых ситуаций:
Сценарий 1: Чат или мессенджер
Вердикт: WebSocket. Задержка критична, сообщения могут лететь пачками. Long Polling создаст лавину запросов при высокой нагрузке. Ktor WebSocket с корутинами справится отлично.
Сценарий 2: Панель мониторинга с обновлением раз в 10-30 секунд
Вердикт: Long Polling. Если данные меняются нечасто, WebSocket будет избыточен. Long Polling проще реализовать, он легче дебажится и не требует специальной инфраструктуры. Плюс вы можете использовать обычный HTTP-кеш.
Сценарий 3: Биржевой терминал или котировки в реальном времени
Вердикт: WebSocket. Тут важна каждая миллисекунда. Long Polling добавит задержку на каждый запрос, что неприемлемо для трейдинга. Используйте Ktor или OkHttp WebSocket.
Сценарий 4: Мобильное приложение с нестабильным интернетом
Вердикт: гибрид. Начните с WebSocket,