WebSocket в Node.js (ws)
Библиотека ws
ws — минималистичная и быстрая реализация WebSocket для Node.js.
npm install ws
Что даёт:
- Сервер и клиент.
- Чистый WebSocket — без fallback.
- Минимум накладных.
- Работает в любом браузере.
Сервер
server.js:
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Новый клиент');
ws.on('message', (data) => {
console.log('Получено:', data.toString());
});
ws.send('Привет от сервера!');
});
console.log('Сервер на ws://localhost:8080');
Что здесь:
WebSocketServer— класс сервера.connection— новое соединение.ws— объект конкретного клиента.message— входящее сообщение.ws.send— отправить.
Клиент в браузере
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Соединение открыто');
ws.send('Привет!');
};
ws.onmessage = (event) => {
console.log('Получено:', event.data);
};
ws.onclose = () => {
console.log('Соединение закрыто');
};
ws.onerror = (error) => {
console.error('Ошибка:', error);
};
Четыре обработчика:
onopen— открытие.onmessage— сообщение.onclose— закрытие.onerror— ошибка.
Клиент в Node.js
import WebSocket from 'ws';
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
ws.send('Привет!');
});
ws.on('message', (data) => {
console.log('Получено:', data.toString());
});
Синтаксис другой — Node-версия использует .on().
JSON-сообщения
Отправка:
ws.send(JSON.stringify({
type: 'chat',
text: 'Привет',
user: 'Иван',
}));
Чтение:
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
switch (message.type) {
case 'chat':
handleChat(message);
break;
case 'typing':
handleTyping(message);
break;
default:
console.warn('Неизвестный тип:', message.type);
}
} catch (e) {
console.error('Некорректный JSON:', data.toString());
}
});
Правило: всегда проверяй JSON — клиент может прислать что угодно.
Broadcast всем
Отправить всем клиентам:
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const text = data.toString();
// Отправить всем
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(text);
}
});
});
});
Что здесь:
wss.clients— Set клиентов.client.readyState— состояние.WebSocket.OPEN— константа.
Состояния соединения
WebSocket.CONNECTING; // 0
WebSocket.OPEN; // 1
WebSocket.CLOSING; // 2
WebSocket.CLOSED; // 3
Проверка:
if (ws.readyState === WebSocket.OPEN) {
ws.send('OK');
}
⚠️ Не отправляй в закрытое соединение — будет ошибка.
Ping/Pong
Проверка живого соединения:
const wss = new WebSocketServer({ port: 8080 });
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
});
wss.on('close', () => {
clearInterval(interval);
});
Что происходит:
- Каждые 30 сек —
ping. - Клиент отвечает
pongавтоматически. - Если
pongне пришёл — соединение мёртвое,terminate.
Зачем: TCP-соединение может быть разорвано (Wi-Fi, мобильная сеть), но сервер этого не знает.
Закрытие
Сервер:
ws.close(1000, 'Нормальное закрытие');
Клиент:
ws.close();
Коды:
- 1000 — нормальное закрытие.
- 1001 — уход.
- 1008 — нарушение политики.
- 1011 — ошибка сервера.
- 4000–4999 — свои коды.
HTTP + WebSocket на одном порту
Используя http.createServer:
import http from 'node:http';
import { WebSocketServer } from 'ws';
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('HTTP работает');
});
const wss = new WebSocketServer({ server });
wss.on('connection', (ws) => {
ws.send('WebSocket работает');
});
server.listen(8080, () => {
console.log('http://localhost:8080 — и HTTP, и WebSocket');
});
Что даёт:
- Один порт для HTTP и WebSocket.
- Один сервер.
- Проще с прокси.
Уникальные ID клиентов
import { randomUUID } from 'node:crypto';
wss.on('connection', (ws) => {
ws.id = randomUUID();
ws.userId = null;
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
// используем ws.id
});
});
Зачем: в комнатах, при персональных сообщениях.
Хранение состояния
const clients = new Map();
wss.on('connection', (ws) => {
const id = randomUUID();
clients.set(id, { ws, userId: null });
ws.on('close', () => {
clients.delete(id);
});
});
В памяти сервера.
⚠️ Не масштабируется на несколько серверов.
Практика: echo-сервер
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Клиент подключился');
ws.on('message', (data) => {
const text = data.toString();
console.log('Получено:', text);
ws.send(`Эхо: ${text}`);
});
ws.on('close', () => {
console.log('Клиент отключился');
});
ws.send('Привет! Отправь что-нибудь.');
});
Проверка:
npx wscat -c ws://localhost:8080
# > hello
# < Эхо: hello
Практика: broadcast-чат
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.send(JSON.stringify({
type: 'system',
text: 'Добро пожаловать в чат',
}));
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
const out = JSON.stringify({
type: 'chat',
user: message.user,
text: message.text,
time: new Date().toISOString(),
});
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(out);
}
});
});
});
Частые ошибки
1. Отправка в закрытое соединение:
ws.send('x'); // если readyState !== OPEN → ошибка
// Правильно:
if (ws.readyState === ws.OPEN) {
ws.send('x');
}
2. Не парсить JSON:
const msg = data; // Buffer, не объект
// Правильно:
const msg = JSON.parse(data.toString());
3. Забыть закрыть соединение:
Клиент отключился, а сервер всё ещё хранит его
Решение: ws.on('close', () => clients.delete(id)).
4. Мёртвые соединения:
Клиент в метро, соединение порвано, а сервер думает, что живой
Решение: ping/pong.
5. Хардкод порта:
new WebSocketServer({ port: 8080 });
// Правильно:
const port = Number(process.env.PORT) || 8080;
new WebSocketServer({ port });
Итог
ws— минималистичная библиотека.WebSocketServer— сервер.connection,message,close,error— события.ws.send— отправить.wss.clients— все клиенты.- Ping/pong — проверка жизни.
- HTTP + WS на одном порту — через
http.createServer. - Состояние — в памяти сервера (не масштабируется).
Дальше — Socket.IO: основы.