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

Ответ

Реализация 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('Соединение закрыто');
  });
});

Ответ 18+ 🔞

А, ну слушай, про WebSocket'ы, да? Это ж та самая магия, когда браузер и сервер начинают болтать без остановки, как две бабки у подъезда. Не то что этот твой HTTP, который раз в минуту: "Дай-дай-дай" и свалил. Тут связь постоянная, живая, как в скайпе, только без этой ебалы с регистрацией.

Так, поехали по косточкам, как это всё работает, а то смотришь в код — нихуя не понятно.

Первое, рукопожатие, но не то, что ты подумал.
Всё начинается с притворства, что это обычный HTTP-запрос. Клиент такой: "Ой, я простой парень, хочу страничку посмотреть". А в заголовках подсовывает: Upgrade: websocket. Это как прийти в баню и сказать: "Я просто погреться", а сам уже полотенце скинул. Сервер, если не мудак, отвечает: "Ага, понял тебя, браток" — статусом 101 и переключается на новую тему разговора. Всё, притворство закончено, можно по-настоящему.

Второе, разговор идёт фреймами, а не сплошным потоком сознания.
После рукопожатия они не просто байтики туда-сюда шлют. Нет, всё чинно-благородно, упаковано в аккуратные фреймы. У каждого фрейма есть шапка: там написано, текст это или бинарник, или команда "всё, закругляемся". А потом уже сами данные. Это как на почте: не кидаешь деньги в конверте просто так, а пишешь, что внутри и сколько.

Третье, состояние помнят, как слоны.
HTTP — он как золотая рыбка. Отправил запрос, получил ответ и всё забыл. А WebSocket — это долгоживущее соединение, сервер должен помнить всех, кто к нему подключился. Представь бармена, который знает твой обычный заказ. Ты пришёл — он уже стаканчик тебе готовит. Сервер так же: открыл соединение — держи его в списке, пока сам не отвалишься.

Четвёртое, пинг-понг, чтобы не уснули.
Чтобы понять, жив ли собеседник на том конце провода, кидают служебные фреймы Ping. Это как ты другу в чате: "Ты живой?" Если он не мудак, ответит Pong: "Живой, живой, не доёбывайся". Если молчит — значит, связь похерилась, можно соединение прибить.

Пятое, культурно прощаются.
Закрывают соединение не просто разрывом TCP, а специальным фреймом Close. Там даже можно код причины указать, типа "я всё, у меня обед" или "ты меня заебал". Вторая сторона должна ответить тем же. Это как не хлопнуть дверью, а сказать "всего доброго" и пожать руку. Цивилизация, блядь.

Ну и чтобы совсем понятно было, вот тебе живой пример на 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('Один лох свалил');
  });
});

Вот и вся магия. Выглядит просто, но если лезть в дебри с ручным разбором фреймов — там можно ебалом об стол биться. Но для обычного использования — проще пареной репы. Главное понять базовые принципы, а остальное — дело техники и документации, которую никто не читает.