WebSocket сервер: ws, Socket.io

WebSocket — протокол полнодуплексной связи через одно постоянное TCP-соединение; ws — минималистичная Node.js реализация, Socket.io — библиотека поверх WebSocket с автоматическим fallback, комнатами и событийной моделью.

Зачем нужно

HTTP — протокол запрос/ответ: сервер не может инициировать отправку данных клиенту. WebSocket открывает постоянное двустороннее соединение, что идеально для real-time приложений: чаты, live-уведомления, совместное редактирование, биржевые котировки, мультиплеер.

Где используется

  • Чаты и мессенджеры
  • Live-уведомления (push-уведомления без polling)
  • Совместное редактирование документов (Google Docs-стиль)
  • Онлайн-игры (мультиплеер)
  • Биржевые терминалы и дашборды с обновлениями в реальном времени

Основной контент

ws — минималистичный WebSocket сервер

npm install ws
// server.js
const { WebSocketServer } = require('ws');
const http = require('http');
const express = require('express');

const app = express;
const server = http.createServer(app);
const wss = new WebSocketServer({ server });

// Хранилище клиентов
const clients = new Map();

wss.on('connection', (ws, req) => {
  const clientId = Date.now();
  clients.set(clientId, ws);
  console.log(`Client ${clientId} connected. Total: ${clients.size}`);

  // Получить сообщение
  ws.on('message', (data) => {
    const message = JSON.parse(data.toString());
    console.log(`Received from ${clientId}:`, message);

    // Broadcast всем клиентам
    for (const [id, client] of clients) {
      if (client.readyState === ws.OPEN) {
        client.send(JSON.stringify({ from: clientId, ...message }));
      }
    }
  });

  // Клиент отключился
  ws.on('close', () => {
    clients.delete(clientId);
    console.log(`Client ${clientId} disconnected`);
  });

  ws.on('error', (err) => console.error(`Client ${clientId} error:`, err));

  // Отправить приветствие
  ws.send(JSON.stringify({ type: 'connected', clientId }));
});

server.listen(3000, () => console.log('Server with WebSocket on port 3000'));
// Клиент (браузер)
const ws = new WebSocket('ws://localhost:3000');
ws.onmessage = (event) => console.log(JSON.parse(event.data));
ws.onopen = () => ws.send(JSON.stringify({ type: 'chat', text: 'Hello!' }));

Socket.io — события, комнаты, broadcast

npm install socket.io
// server.js
const { Server } = require('socket.io');
const express = require('express');
const http = require('http');

const app = express;
const server = http.createServer(app);
const io = new Server(server, {
  cors: { origin: 'http://localhost:3000', methods: ['GET', 'POST'] }
});

io.on('connection', (socket) => {
  console.log(`User connected: ${socket.id}`);

  // Войти в комнату
  socket.on('join-room', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', { userId: socket.id });
  });

  // Сообщение в чат (в комнату)
  socket.on('chat-message', ({ roomId, text }) => {
    io.to(roomId).emit('chat-message', {
      from: socket.id,
      text,
      timestamp: new Date
    });
  });

  // Broadcast всем кроме отправителя
  socket.on('typing', (data) => {
    socket.broadcast.emit('user-typing', { userId: socket.id });
  });

  socket.on('disconnect', () => {
    console.log(`User disconnected: ${socket.id}`);
  });
});

server.listen(3000);
// Клиент (browser)
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');

socket.emit('join-room', 'room-42');
socket.on('chat-message', (msg) => console.log(msg));
socket.emit('chat-message', { roomId: 'room-42', text: 'Hello!' });

Масштабирование — Socket.io с Redis adapter

npm install @socket.io/redis-adapter ioredis
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate;

await Promise.all([pubClient.connect, subClient.connect]);
io.adapter(createAdapter(pubClient, subClient));
// Теперь несколько Node.js-процессов синхронизируют события через Redis

Частые ошибки

  • Не закрывать соединение при ошибке — зависшие соединения потребляют память; настроить ws.terminate по таймауту
  • Хранить состояние в памяти сервера — при горизонтальном масштабировании клиент может попасть на другой инстанс; использовать Redis adapter
  • Не валидировать данные через WebSocket — входящие данные могут быть любыми; всегда парсить и валидировать как JSON
  • Использовать Socket.io там где достаточно wsSocket.io добавляет 40+ KB клиентской библиотеки; для простых случаев нативного WebSocket API хватает

Связанные темы

Ресурсы


🎓 Источник: WebSocket сервер на Node.js (электронные таблицы и чат)

  • 📅 2018-10-31 · YouTube
  • Тезисы:
    • URL-схема ws:// или wss:// (secure); путь можно использовать как комнату.
    • WebSocket-библиотека пропатчивает экземпляр http.Server — добавляет поддержку HTTP Upgrade.
    • autoAcceptConnections: false → ловим request и явно вызываем request.accept.
    • Сообщения приходят с типом: utf8Data (текст) или binaryData (Buffer).
    • Список подключений храним для broadcast; рассылка всем кроме отправителя — паттерн чата.
  • Цитата: «Одна библиотека может влезть в кишки другой — WebSocket пропатчивает HTTP-сервер, чтобы понимать HTTP Upgrade.»

🎓 Источник: Ревью кода Websocket на JavaScript для Node.js (metacom)

  • 📅 2025-08-30 · YouTube
  • Тезисы:
    • Транспорт WebSocket прозрачен для API: тот же контракт что HTTP, только двусторонний.
    • Бинарный фрейм быстрее текстового; для JSON важна сериализация структурированно.
    • ws.ping/pong для keep-alive; зависшие соединения отрезать terminate по таймауту.