Разработка
WebSocket: полный гид по real-time коммуникации
Как построить real-time функциональность: архитектура WebSocket, масштабирование, fallback стратегии и безопасность.
ИК
Игор Кривошей
Full-stack инженер
15 лютого 2026 р.·12 мин

Что такое WebSocket?
WebSocket обеспечивает полнодуплексную связь между клиентом и сервером через одно долгоживущее соединение:
- Мгновенная доставка — нет HTTP overhead
- Двунаправленность — сервер может инициировать сообщения
- Эффективность — одно соединение вместо множества запросов
Базовая реализация
Сервер на Node.js
TYPESCRIPT
import { WebSocketServer, WebSocket } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws: WebSocket) => {
console.log('New client connected');
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
// Рассылка всем клиентам
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});Клиент
TYPESCRIPT
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onclose = () => {
console.log('Disconnected');
// Реализуйте reconnect логику
};Масштабирование WebSocket
Проблема: несколько серверов
Когда клиенты подключены к разным серверам, нужен брокер сообщений.
Redis Pub/Sub для масштабирования
TYPESCRIPT
import Redis from 'ioredis';
const pub = new Redis();
const sub = new Redis();
// Подписываемся на канал
sub.subscribe('broadcast');
sub.on('message', (channel, message) => {
// Отправляем всем локальным клиентам
wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
});
});
// Публикуем сообщение
function broadcast(message: any) {
pub.publish('broadcast', JSON.stringify(message));
}Архитектура комнат (Rooms)
Изоляция пользователей по группам:
TYPESCRIPT
class RoomManager {
private rooms = new Map<string, Set<WebSocket>>();
join(room: string, ws: WebSocket) {
if (!this.rooms.has(room)) {
this.rooms.set(room, new Set());
}
this.rooms.get(room)!.add(ws);
}
leave(room: string, ws: WebSocket) {
this.rooms.get(room)?.delete(ws);
}
broadcast(room: string, message: any) {
const clients = this.rooms.get(room);
clients?.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
});
}
}Heartbeat и reconnect
Keep-alive
TYPESCRIPT
// Сервер: heartbeat
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) {
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
ws.on('pong', () => {
ws.isAlive = true;
});Автоматический reconnect
TYPESCRIPT
class WebSocketClient {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000;
connect() {
this.ws = new WebSocket(this.url);
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, this.reconnectDelay * this.reconnectAttempts);
}
};
this.ws.onopen = () => {
this.reconnectAttempts = 0;
};
}
}Безопасность
Аутентификация
Проверяйте токен при подключении:
TYPESCRIPT
import jwt from 'jsonwebtoken';
wss.on('connection', (ws, req) => {
const token = new URL(req.url!, 'http://localhost')
.searchParams.get('token');
try {
const decoded = jwt.verify(token!, process.env.JWT_SECRET!);
(ws as any).userId = decoded.sub;
} catch {
ws.close(1008, 'Invalid token');
}
});Rate limiting
TYPESCRIPT
// Ограничение сообщений от клиента
const messageLimit = new Map<string, number>();
ws.on('message', (data) => {
const userId = (ws as any).userId;
const count = messageLimit.get(userId) || 0;
if (count > 100) {
ws.close(1008, 'Rate limit exceeded');
return;
}
messageLimit.set(userId, count + 1);
});Fallback: Server-Sent Events
Для однонаправленной связи (server → client) используйте SSE:
TYPESCRIPT
// Клиент
const eventSource = new EventSource('/api/sse');
eventSource.onmessage = (event) => {
console.log('Update:', JSON.parse(event.data));
};Заключение
WebSocket идеален для:
- Чатов и мессенджеров
- Коллаборативных редакторов
- Игр в реальном времени
- Live-данных (курсы, аукционы)
Помните:
- Используйте Redis для масштабирования
- Реализуйте heartbeat и reconnect
- Ограничивайте rate на сообщения
- Используйте SSE для однонаправленной связи
Теги
WebSocketReal-timeBackendNode.js