awesome-everything EN
↑ Обратно к восхождению

Сети и протоколы

WebSocket vs SSE vs long-polling: выбор правильного транспорта

Суть Сравнение задержки, накладных расходов и совместимости с прокси для WebSocket, Server-Sent Events и long-polling — чтобы выбрать инструмент до начала разработки.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 10 min

Прежде чем тянуться за WebSocket, спросите: действительно ли нужна двусторонняя связь? SSE покрывает 90% сценариев «сервер должен пушить» — без собственной логики реконнекта и с куда более дружелюбными прокси. Неправильный выбор транспорта стоит недель отладки или ненужной сложности.

Три транспорта, три модели

WebSocketSSELong-polling
НаправлениеДвунаправленныйТолько сервер→клиентСервер→клиент (клиент опрашивает)
ПротоколБинарный фрейм (RFC 6455)text/event-stream по HTTPОбычный HTTP
Накладные расходы2–6 байт на фрейм~5 байт + метаданные на событие~500+ байт заголовков на запрос
Задержка0–5 мс3–8 мсИнтервал опроса + RTT (100–60000 мс)
РеконнектПриложение должно реализоватьБраузерный EventSource обрабатываетПриложение переоткрывает запрос
Поддержка проксиТребует поддержки UpgradeРаботает с любым HTTP-проксиРаботает везде

WebSocket — full-duplex, минимальные накладные расходы

После upgrade-handshake любая сторона отправляет фреймы в любой момент. Накладные расходы на сообщение: 2–6 байт. Сервер может пушить без запроса клиента; клиент может отправлять без нового HTTP-соединения. Это правильный выбор когда:

  • Клиент часто отправляет данные (игровой ввод, совместное редактирование, торговые заявки).
  • Бюджет задержки менее 10 мс.
  • Вы контролируете прокси/инфраструктуру (или знаете, что она поддерживает Upgrade).

Минус: нет встроенного реконнекта. Если соединение обрывается (idle timeout прокси, сетевой глюк), код должен обнаружить close-код 1006 и переподключиться с backoff.

SSE — server push, браузер обрабатывает всё

SSE использует обычный постоянный HTTP-ответ с Content-Type: text/event-stream. Сервер пишет события, разделённые переводом строки:

event: price-update
data: {"symbol":"AAPL","price":182.34}
id: 4291

Нативный API браузера EventSource парсит события и автоматически переподключается с заголовком Last-Event-ID при обрыве соединения. Задержка: 3–8 мс. Ноль кода реконнекта писать не нужно. Работает через любой HTTP/1.1-прокси, включая корпоративные файрволы.

SSE неподходящ когда:

  • Клиенту нужно отправлять данные (нужен отдельный REST-эндпоинт, что неудобно).
  • Нужны бинарные payload (SSE — только текст).

Long-polling — универсальный запасной вариант

Клиент открывает HTTP-запрос, сервер держит его открытым до появления данных (или до таймаута). Когда сервер отвечает, клиент сразу открывает новый запрос. Это симулирует server push через обычный HTTP.

Задержка = время ожидания сервера + один RTT. В лучшем случае 100 мс для сервера, ответившего сразу. В худшем — 60+ секунд для сервера с длинным таймаутом перед пустым ответом.

Накладные расходы высоки: каждый опрос несёт полные HTTP-заголовки (~500+ байт). При 50 000 клиентов, опрашивающих каждые 100 мс — 500 000 HTTP-запросов/секунду. Ни одно современное приложение не использует long-polling как основной транспорт — это graceful fallback для сред, блокирующих WebSocket.

Сравнение задержки и накладных расходов транспортов
Заголовок WebSocket-фрейма (сервер→клиент)
2 байта
Задержка WebSocket-сообщения после handshake
0–5 мс
Накладные расходы SSE-события (минимум)
~5 байт + метаданные
Задержка SSE-сообщения
3–8 мс
HTTP-заголовки long-poll на запрос
~500+ байт
Эффективная задержка long-poll (лучший случай)
100–500 мс

Согласование subprotocol и расширений

Клиент может запросить subprotocol прикладного уровня во время upgrade:

Sec-WebSocket-Protocol: chat, superchat

Сервер выбирает один и возвращает его обратно. Subprotocol’ы позволяют командам версионировать формат сообщений не затрагивая WebSocket-уровень — chat использует JSON, superchat — компактный бинарный формат. Если сервер не возвращает subprotocol-заголовок, соединение не имеет именованного протокола (это допустимо; приложение неявно определяет формат сообщений).

Расширения работают аналогично через Sec-WebSocket-Extensions. Важнейшее расширение на практике — permessage-deflate (RFC 7692): DEFLATE-сжатие на сообщение. На текстовых payload (JSON, HTML) типичная степень сжатия 50–90%. На маленьких сообщениях (< 64 байт) сжатие может увеличить payload. На уже сжатых данных (изображения, видео) — никакого эффекта. Затраты памяти значительны: при настройках по умолчанию каждый компрессор потребляет ~256 КБ, каждый декомпрессор ~44 КБ — при 100k idle-соединений это ~25 ГБ RAM только на состояние сжатия. Большинство production-инсталляций отключают permessage-deflate по умолчанию.

Почему это работает

Почему SSE недооценён. SSE стандартизирован в HTML5 в 2009 году, но в основном игнорировался, потому что WebSocket появился одновременно и казался более интересным. На практике SSE чисто покрывает большинство сценариев уведомлений, лент и дашбордов: сервер пушит события, браузер переподключается автоматически, специальная настройка прокси не нужна. Случаи, действительно требующие WebSocket, — это когда клиент часто отправляет данные: игры, совместные редакторы, торговые терминалы.

Викторина

У компании 1 000 WebSocket-соединений. Старый подход открывает 1 000 TCP-соединений к бэкенду, каждое потребляет ~10 КБ памяти в idle. Переход на HTTP/2 extended CONNECT (RFC 8441) мультиплексирует все WebSocket-соединения через одно TCP-соединение, потребляющее 50 КБ суммарно. Какова приблизительная экономия памяти?

Выбери лучший вариант

Система уведомлений пушит ~5 обновлений в минуту на 100k пользователей. Пользователи не отправляют данные серверу. Какой транспорт?

Вспомните перед уходом
  1. 01
    Назовите два сценария, где SSE лучше WebSocket, и объясните почему.
  2. 02
    Почему permessage-deflate потребляет столько памяти в масштабе и как это решается в production?
  3. 03
    Браузер отправляет данные серверу каждые 50 мс (например, позиции мыши для совместного редактора). Почему SSE недостаточен и какой правильный транспорт?
Итог

Три транспорта решают задачу «серверу нужно пушить» с разными компромиссами. WebSocket предлагает минимальную задержку (0–5 мс) и истинную двунаправленность — правильный выбор когда клиенты часто отправляют данные. SSE стримит text/event-stream через постоянный HTTP-ответ, браузерный EventSource обрабатывает реконнект через Last-Event-ID автоматически — правильный выбор для server-push-only сценариев в средах с прокси. Long-polling симулирует push через обычный HTTP при 500+ байтах накладных расходов заголовков на опрос и ~100–500 мс эффективной задержки — это универсальный запасной вариант, не основной транспорт. WebSocket subprotocol’ы позволяют версионировать схему сообщений; расширение permessage-deflate добавляет 50–90% текстового сжатия, но стоит ~256 КБ RAM на контекст компрессора соединения.

Связанные уроки
встречается в162
Продолжить восхождение ↑Backpressure в WebSocket: когда клиенты не успевают
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.