Транспорт бэкенда
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 только при необходимости?