Антон Ларичев

Введение
Когда пользователь открывает чат или смотрит на обновляющийся в реальном времени дашборд, за этим стоит особый протокол — WebSocket. В отличие от стандартного HTTP, он позволяет серверу и клиенту обмениваться данными без постоянных запросов. В этой статье разберём, что такое WebSocket, как устроено соединение изнутри, и напишем простой пример на Node.js.
Что такое WebSocket
WebSocket — это протокол двусторонней связи поверх TCP, стандартизированный в RFC 6455. Он решает фундаментальную проблему HTTP: клиент всегда должен инициировать запрос, а сервер не может сам «отправить» данные.
С WebSocket соединение устанавливается один раз и остаётся открытым. Обе стороны могут отправлять сообщения в любой момент — это называется full-duplex (полнодуплексная) связь.
Типичные сценарии применения:
- чаты и мессенджеры
- онлайн-игры
- торговые терминалы с котировками в реальном времени
- совместное редактирование документов
- push-уведомления и live-обновления
Как работает WebSocket
Handshake — установка соединения
WebSocket начинается с обычного HTTP-запроса. Клиент отправляет заголовки с просьбой «обновить» соединение:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Сервер отвечает кодом 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
После этого HTTP-соединение «превращается» в WebSocket. Никакого нового TCP-соединения не создаётся — используется то же самое.
Обмен данными — фреймы
Данные передаются в виде фреймов (frames). Каждый фрейм содержит:
- тип данных (текст, бинарные данные, ping/pong, закрытие соединения)
- длину полезной нагрузки
- маску (клиент обязан маскировать данные, сервер — нет)
Для прикладной разработки детали фреймирования скрыты библиотеками, но понимать их полезно при отладке трафика в DevTools.
Реализация на Node.js
Для работы с WebSocket в Node.js чаще всего используют библиотеку ws.
npm install ws
Серверная часть
const { WebSocketServer } = require('ws');
// Создаём WebSocket-сервер на порту 8080
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
console.log('Клиент подключился');
// Обрабатываем входящие сообщения
socket.on('message', (data) => {
const message = data.toString();
console.log('Получено:', message);
// Рассылаем сообщение всем подключённым клиентам
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(`Эхо: ${message}`);
}
});
});
socket.on('close', () => {
console.log('Клиент отключился');
});
// Отправляем приветствие сразу при подключении
socket.send('Добро пожаловать в чат!');
});
Клиентская часть
В браузере WebSocket поддерживается нативно через встроенный API — никаких библиотек не нужно:
// Подключаемся к серверу
const socket = new WebSocket('ws://localhost:8080');
// Соединение успешно установлено
socket.addEventListener('open', () => {
console.log('Соединение установлено');
socket.send('Привет, сервер!');
});
// Получаем сообщения от сервера
socket.addEventListener('message', (event) => {
console.log('Сообщение от сервера:', event.data);
});
// Соединение закрыто
socket.addEventListener('close', (event) => {
console.log('Соединение закрыто, код:', event.code);
});
// Ошибки соединения
socket.addEventListener('error', (error) => {
console.error('Ошибка WebSocket:', error);
});
Сравнение с HTTP
| Характеристика | HTTP | WebSocket |
|---|---|---|
| Инициатор запроса | Только клиент | Клиент и сервер |
| Соединение | Новое на каждый запрос | Одно постоянное |
| Накладные расходы | Заголовки на каждый запрос | Минимальные фреймы |
| Кэширование | Поддерживается | Не применимо |
| Подходит для | REST API, загрузка страниц | Real-time данные |
Для сценариев, где сервер отправляет обновления, а клиент только слушает, стоит также рассмотреть Server-Sent Events (SSE) — более простой протокол поверх обычного HTTP.
Частые ошибки
Нет обработчика события error. Без него приложение упадёт с необработанным исключением при сетевом сбое. Всегда добавляйте socket.on('error', handler) на сервере.
Не закрывать «мёртвые» соединения. Если клиент закрывает вкладку без явного закрытия соединения, оно остаётся в wss.clients до таймаута. Используйте механизм ping/pong:
// Проверяем живость клиентов каждые 30 секунд
const interval = setInterval(() => {
wss.clients.forEach((client) => {
if (client.isAlive === false) {
return client.terminate(); // Закрываем зависшее соединение
}
client.isAlive = false;
client.ping();
});
}, 30000);
wss.on('connection', (socket) => {
socket.isAlive = true;
// Клиент ответил на ping — он жив
socket.on('pong', () => {
socket.isAlive = true;
});
});
Отправка без проверки состояния. Вызов socket.send() на закрытом соединении выбрасывает ошибку. Перед отправкой всегда проверяйте socket.readyState === WebSocket.OPEN.
Игнорирование масштабирования. При горизонтальном масштабировании клиент на сервере A не получит сообщение, отправленное сервером B. Для решения используют брокеры сообщений — Redis Pub/Sub или RabbitMQ.
Заключение
WebSocket решает задачу двусторонней коммуникации в реальном времени, которую HTTP решить не может. Одно постоянное соединение вместо сотен запросов даёт низкую задержку и высокую эффективность. Библиотека ws делает реализацию в Node.js простой: несколько строк кода — и у вас работающий сервер. Главное — помнить об обработке ошибок, ping/pong для обнаружения разорванных соединений и про архитектурные ограничения при масштабировании на несколько инстансов.






Комментарии
0