Какие принципы реализации протокола WebSocket ты знаешь?

«Какие принципы реализации протокола WebSocket ты знаешь?» — вопрос из категории Сети, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Реализация WebSocket-сервера строится на нескольких ключевых принципах, обеспечивающих двустороннюю, низколатентную связь.

Основные принципы:

  1. Handshake (рукопожатие) через HTTP.

    • Соединение начинается с обычного HTTP-запроса GET с заголовками Upgrade: websocket и Connection: Upgrade.
    • Сервер отвечает статусом 101 Switching Protocols, подтверждая переход на протокол WebSocket.
    • Пример заголовков клиента:
      GET /chat HTTP/1.1
      Host: server.example.com
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
      Sec-WebSocket-Version: 13
  2. Фреймовая структура данных.

    • После рукопожатия данные передаются не потоком байт, а структурированными фреймами.
    • Каждый фрейм содержит заголовок (управляющая информация: тип фрейма, маскировка, длина) и полезную нагрузку (payload).
    • Типы фреймов: текстовый (0x1), бинарный (0x2), закрытие соединения (0x8), ping (0x9), pong (0xA).
  3. Поддержка состояния (Stateful).

    • В отличие от HTTP, WebSocket-соединение является долгоживущим и сохраняет состояние между обменами сообщениями. Сервер должен отслеживать все активные соединения.
  4. Контроль за соединением (Ping/Pong).

    • Для проверки живости соединения используются служебные фреймы Ping и Pong. Сервер или клиент может отправить Ping и ожидать Pong в ответ.
  5. Безопасное закрытие.

    • Закрытие инициируется фреймом типа Close (0x8), который может содержать код причины и описание. Вторая сторона должна ответить таким же фреймом.

Практический пример на Node.js с библиотекой ws:

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// Принцип 1 & 3: Обработка рукопожатия и хранение состояния
wss.on('connection', function connection(ws) {
  console.log('Новое соединение установлено');

  // Принцип 2: Обработка входящих сообщений (фреймов)
  ws.on('message', function incoming(message) {
    console.log('Получено: %s', message);
    // Широковещательная рассылка всем подключенным клиентам
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });

  // Принцип 5: Обработка закрытия соединения
  ws.on('close', function close() {
    console.log('Соединение закрыто');
  });
});