Разработка

WebSocket: полный гид по real-time коммуникации

Как построить real-time функциональность: архитектура WebSocket, масштабирование, fallback стратегии и безопасность.

ИК

Игор Кривошей

Full-stack инженер

15 лютого 2026 р.·12 мин
WebSocket: полный гид по real-time коммуникации

Что такое 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

Shipstack

Готовы внедрить это на практике?

Обсудим архитектуру, сроки и стек — без шаблонных презентаций, по делу.