Транспорт бэкенда

WebSocket: двусторонняя связь в реальном времени

Slack отправляет тебе сообщение - ты видишь его через 50 миллисекунд. Не через 2 секунды polling, не через HTTP long-poll с таймаутом - а именно 50 мс. Это WebSocket: одно TCP-соединение, открытое часами, через которое данные летят в обе стороны без переустановки.

  • **Discord** держит более 8 миллионов одновременных WebSocket-соединений. Весь real-time слой - presence, typing indicators, голосовые события - через WS.
  • **Figma** использует WebSocket для синхронизации курсоров и изменений документа между участниками. Задержка < 100 мс при совместном редактировании.
  • **Binance** и другие биржи раздают биржевой стакан (order book) через WebSocket с обновлениями каждые 100 мс - REST polling здесь физически невозможен.

WebSocket Handshake

WebSocket начинается как обычный HTTP-запрос с заголовком `Upgrade: websocket`. Сервер отвечает `101 Switching Protocols` - и с этого момента TCP-соединение превращается в двунаправленный канал. HTTP больше не используется: данные идут в обе стороны без дополнительных рукопожатий.

`Sec-WebSocket-Accept` - это SHA-1 от ключа клиента + GUID. Это защита от случайного подключения браузера к WebSocket-серверу через обычный HTTP-кэш.

Какой HTTP-статус возвращает сервер при успешном WebSocket handshake?

WebSocket Frames и протокол

После handshake данные передаются фреймами (frames). Каждый фрейм имеет заголовок от 2 до 14 байт и payload. Ключевые поля: `FIN` (последний фрейм сообщения), `opcode` (тип: text=0x1, binary=0x2, ping=0x9, pong=0xA, close=0x8), `MASK` (клиент всегда маскирует данные XOR-ключом).

Маскировка клиентских данных (MASK=1) обязательна по RFC 6455 - защита от proxy-серверов, которые могут кэшировать HTTP-ответы и быть обмануты specially crafted WebSocket-фреймом.

Зачем клиент маскирует WebSocket-фреймы XOR-ключом?

Масштабирование WebSocket

WebSocket - stateful соединение. Каждый клиент привязан к конкретному серверу. Это ломает горизонтальное масштабирование: если 1M пользователей распределены по 10 серверам, сообщение для пользователя на сервере #3 не может прийти через сервер #7.

Один Node.js сервер комфортно держит ~50K-100K WebSocket-соединений при idle. С активным трафиком - меньше. uWebSockets.js (используется в этом проекте) - ~10x быстрее встроенного `ws`.

Почему sticky sessions (ip_hash в nginx) - неидеальное решение для WebSocket масштабирования?

Socket.IO vs нативный WebSocket

Socket.IO - это библиотека поверх WebSocket с автоматическим fallback на long-polling, комнатами, namespace и автореконнектом. Нативный WebSocket API - стандарт браузера без дополнительных абстракций.

Socket.IO НЕ совместим с нативным WebSocket - клиент Socket.IO не может подключиться к нативному WS-серверу. При использовании современных браузеров polling fallback обычно не нужен: нативный WebSocket поддерживается везде с 2012.

Что происходит, если подключить нативный WebSocket-клиент к Socket.IO серверу?

WebSocket vs Server-Sent Events

Server-Sent Events (SSE) - односторонний поток событий от сервера к клиенту через обычный HTTP. Проще WebSocket: нет handshake, нет фреймирования, работает через любой HTTP/1.1 прокси. Браузер автоматически переподключается при разрыве.

HTTP/2 снимает ограничение в 6 параллельных SSE-соединений браузера (они мультиплексируются). Для чат-приложений - WebSocket; для push-уведомлений от сервера без ответа от клиента - SSE проще.

WebSocket всегда лучше SSE, раз он двусторонний

SSE проще для однонаправленных потоков: меньше кода, автореконнект, работает через любой HTTP-прокси без настройки

Выбор протокола определяется паттерном коммуникации. Если клиент только слушает - WebSocket добавляет сложность без преимуществ.

В каком случае SSE предпочтительнее WebSocket?

Ключевые идеи

  • **Handshake** - WebSocket начинается как HTTP Upgrade (101), затем TCP становится двунаправленным каналом без HTTP overhead.
  • **Масштабирование** - stateful соединения требуют Pub/Sub через Redis/NATS для синхронизации между несколькими серверами.
  • **SSE vs WS** - для push-only от сервера SSE проще; WebSocket нужен только когда клиент тоже отправляет данные в реальном времени.

Связанные темы

WebSocket стоит рядом с другими протоколами реального времени и паттернами масштабирования:

  • HTTP/2 и мультиплексирование — HTTP/2 решает некоторые проблемы HTTP/1.1 (несколько запросов), но не заменяет WebSocket для server-push сценариев
  • Kafka и асинхронные брокеры — WebSocket доставляет события клиенту в реальном времени; Kafka часто является источником этих событий в бэкенде

Вопросы для размышления

  • Как бы архитектура WebSocket изменилась, если бы HTTP был stateful по умолчанию?
  • Почему Discord выбрал Elixir, а не Node.js для WebSocket-сервера при 1M+ соединениях?
  • В каких сценариях стоит начинать с SSE и мигрировать на WebSocket только при необходимости?

Связанные уроки

  • net-36-websocket
WebSocket: двусторонняя связь в реальном времени

0

1

Войти