WebSocket
Зачем нужно
WebSocket — протокол полнодуплексной связи поверх TCP. В отличие от HTTP (запрос → ответ), WebSocket позволяет серверу отправлять данные клиенту в любой момент без запроса. Это делает WebSocket идеальным для real-time приложений.
Где используется
- Чаты и мессенджеры
- Онлайн-игры
- Торговые платформы (котировки в реальном времени)
- Совместное редактирование (Google Docs)
- Уведомления в реальном времени
- Live-мониторинг и дашборды
HTTP vs WebSocket
HTTP (полудуплекс):
Клиент → Запрос → Сервер
Клиент ← Ответ ← Сервер
Клиент → Запрос → Сервер
Клиент ← Ответ ← Сервер
(Каждый раз новый запрос)
WebSocket (полный дуплекс):
Клиент → HTTP Upgrade → Сервер (handshake)
Клиент ↔ Данные ↔ Сервер (постоянное соединение)
Клиент ← Сообщение ← Сервер (сервер инициирует)
Клиент → Сообщение → Сервер (клиент инициирует)
Жизненный цикл соединения
1. Handshake (HTTP Upgrade):
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
2. Соединение открыто → обмен сообщениями
3. Закрытие (любая сторона может инициировать)
WebSocket API (клиент)
// === Создание соединения ===
const ws = new WebSocket('ws://localhost:3000');
// Для HTTPS: wss://example.com (шифрованное)
// === События ===
// Соединение установлено
ws.addEventListener('open', (event) => {
console.log('Подключено!');
ws.send('Привет, сервер!');
});
// Получено сообщение
ws.addEventListener('message', (event) => {
console.log('Получено:', event.data);
// Если данные в JSON
const data = JSON.parse(event.data);
console.log(data);
});
// Соединение закрыто
ws.addEventListener('close', (event) => {
console.log('Отключено:', event.code, event.reason);
// event.code — код закрытия (1000 = нормальное)
// event.wasClean — true если закрытие чистое
});
// Ошибка
ws.addEventListener('error', (event) => {
console.error('Ошибка WebSocket:', event);
});
// === Отправка данных ===
ws.send('Текстовое сообщение');
ws.send(JSON.stringify({ type: 'chat', text: 'Привет!' }));
ws.send(new Blob(['бинарные данные']));
// === Закрытие ===
ws.close(1000, 'Нормальное закрытие');
// === Свойства ===
ws.readyState; // 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
ws.bufferedAmount; // Байт в очереди на отправку
WebSocket сервер (Node.js)
npm install ws
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 3000 });
// Хранилище подключённых клиентов
const clients = new Set();
wss.on('connection', (ws, request) => {
console.log('Новое подключение:', request.socket.remoteAddress);
clients.add(ws);
// Отправить приветствие
ws.send(JSON.stringify({
type: 'welcome',
message: 'Добро пожаловать!',
online: clients.size,
}));
// Получение сообщения от клиента
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('Получено:', message);
// Рассылка всем (broadcast)
for (const client of clients) {
if (client !== ws && client.readyState === 1) {
client.send(JSON.stringify({
type: 'chat',
from: message.from,
text: message.text(),
timestamp: Date.now(),
}));
}
}
});
// Закрытие соединения
ws.on('close', (code, reason) => {
clients.delete(ws);
console.log(`Отключился: ${code}`);
});
ws.on('error', (err) => {
console.error('Ошибка:', err);
clients.delete(ws);
});
});
console.log('WebSocket сервер на ws://localhost:3000');
Практический пример: чат
// === Клиент: chat.js ===
class ChatClient {
constructor(url, username) {
this.username = username;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnect = 5;
this.connect(url);
}
connect(url) {
this.ws = new WebSocket(url);
this.ws.onopen = () => {
console.log('Подключено к чату');
this.reconnectAttempts = 0;
this.ws.send(JSON.stringify({
type: 'join',
username: this.username,
}));
};
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
this.handleMessage(msg);
};
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnect) {
this.reconnectAttempts++;
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000);
console.log(`Переподключение через ${delay}ms...`);
setTimeout( => this.connect(url), delay);
}
};
}
sendMessage(text) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'message',
text,
username: this.username,
}));
}
}
handleMessage(msg) {
switch (msg.type) {
case 'message':
console.log(`${msg.username}: ${msg.text()}`);
break;
case 'join':
console.log(`${msg.username} вошёл в чат`);
break;
case 'leave':
console.log(`${msg.username} вышел из чата`);
break;
}
}
disconnect {
this.maxReconnect = 0; // Отключить реконнект
this.ws.close(1000, 'Пользователь вышел');
}
}
const chat = new ChatClient('ws://localhost:3000', 'Антон');
chat.sendMessage('Всем привет!');
Heartbeat (keep-alive)
// Сервер: отправляет ping каждые 30 секунд
const HEARTBEAT_INTERVAL = 30000;
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true; // Клиент ответил
});
});
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) {
return ws.terminate; // Не ответил → мёртвое соединение
}
ws.isAlive = false;
ws.ping; // Отправить ping
});
}, HEARTBEAT_INTERVAL);
wss.on('close', () => clearInterval(interval));
Частые ошибки
- Нет реконнекта — соединение обрывается, клиент не переподключается
- Нет heartbeat — мёртвые соединения висят, утечка ресурсов
- JSON.parse без try/catch — бинарные или некорректные данные → crash
- Нет проверки readyState — отправляют в закрытое соединение → ошибка
- Одно соединение на всех — нет разделения по комнатам/каналам
- Забывают wss:// — ws:// работает только без HTTPS, для production нужен wss://
Практика
- Создать WebSocket-сервер на
ws(Node.js) - Подключиться из браузера, отправлять/получать сообщения
- Реализовать broadcast — отправку всем подключённым клиентам
- Добавить автореконнект с exponential backoff
- Собрать простой чат: ввод → отправка → отображение у всех
Связанные темы
- HTTP протокол — WebSocket начинается как HTTP Upgrade
- Клиент-серверное взаимодействие — альтернативные способы связи
- http — HTTP-сервер в Node.js
Ресурсы
- MDN — WebSocket API
- MDN — Writing WebSocket servers
- ws — Node.js WebSocket library
- Socket.IO — библиотека с fallback и rooms
🎓 Источник: WebSocket сервер на Node.js
- 📅 2018-10-31 · YouTube
- Тезисы:
- URL
ws:///wss://(secure); путь URL можно использовать как room. - WebSocket-сервер пропатчивает
http.Server— добавляет обработку HTTP Upgrade. request→acceptдля принятия соединения (сautoAcceptConnections: false).- Сообщения с типом:
utf8DataилиbinaryData. - Broadcast рассылкой по списку всех подключённых клиентов.
- URL
- Цитата: «WebSocket пропатчивает HTTP-сервер, чтобы он понимал заголовки HTTP Upgrade.»