Ответ
Реализация WebSocket-сервера строится на нескольких ключевых принципах, обеспечивающих двустороннюю, низколатентную связь.
Основные принципы:
-
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
- Соединение начинается с обычного HTTP-запроса
-
Фреймовая структура данных.
- После рукопожатия данные передаются не потоком байт, а структурированными фреймами.
- Каждый фрейм содержит заголовок (управляющая информация: тип фрейма, маскировка, длина) и полезную нагрузку (payload).
- Типы фреймов: текстовый (
0x1), бинарный (0x2), закрытие соединения (0x8), ping (0x9), pong (0xA).
-
Поддержка состояния (Stateful).
- В отличие от HTTP, WebSocket-соединение является долгоживущим и сохраняет состояние между обменами сообщениями. Сервер должен отслеживать все активные соединения.
-
Контроль за соединением (Ping/Pong).
- Для проверки живости соединения используются служебные фреймы Ping и Pong. Сервер или клиент может отправить Ping и ожидать Pong в ответ.
-
Безопасное закрытие.
- Закрытие инициируется фреймом типа
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('Один лох свалил');
});
});
Вот и вся магия. Выглядит просто, но если лезть в дебри с ручным разбором фреймов — там можно ебалом об стол биться. Но для обычного использования — проще пареной репы. Главное понять базовые принципы, а остальное — дело техники и документации, которую никто не читает.