Назад к блогу
Backend

WebSocket и реалтайм: чаты, уведомления и live-данные

Создание real-time приложений с WebSocket и Socket.io: чаты, уведомления, live-обновления, SSE и лучшие практики.

5 января 2026 г.
10 мин чтения
133 просмотров
MOLOTILO

MOLOTILO DIGITAL

WebSocket и реалтайм: чаты, уведомления и live-данные

Зачем нужен real-time

Современные приложения требуют мгновенной доставки данных: чаты, уведомления, live-обновления, совместное редактирование. HTTP не подходит для этого — нужен WebSocket или SSE.

WebSocket vs HTTP

WebSocket — протокол двусторонней связи поверх TCP. В отличие от HTTP, соединение остаётся открытым:

  • HTTP — запрос-ответ, каждый раз новое соединение
  • WebSocket — постоянное соединение, двусторонний обмен
  • SSE (Server-Sent Events) — односторонний поток от сервера

Socket.io — библиотека для real-time

Socket.io упрощает работу с WebSocket и добавляет fallback на polling:

// server.ts
import { Server } from 'socket.io';
import { createServer } from 'http';

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

// Подключение клиента
io.on('connection', (socket) => {
  console.log('User connected:', socket.id);
  
  // Присоединение к комнате
  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', { id: socket.id });
  });
  
  // Получение сообщения
  socket.on('message', (data: { roomId: string; text: string }) => {
    io.to(data.roomId).emit('message', {
      id: Date.now(),
      text: data.text,
      userId: socket.id,
      timestamp: new Date()
    });
  });
  
  // Typing индикатор
  socket.on('typing', (roomId: string) => {
    socket.to(roomId).emit('user-typing', socket.id);
  });
  
  // Отключение
  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

httpServer.listen(4000);

Клиент Socket.io

// hooks/useSocket.ts
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';

export function useSocket(roomId: string) {
  const [socket, setSocket] = useState(null);
  const [messages, setMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);

  useEffect(() => {
    const newSocket = io('http://localhost:3000');
    
    newSocket.on('connect', () => {
      setIsConnected(true);
      newSocket.emit('join-room', roomId);
    });
    
    newSocket.on('message', (message: Message) => {
      setMessages(prev => [...prev, message]);
    });
    
    newSocket.on('disconnect', () => {
      setIsConnected(false);
    });
    
    setSocket(newSocket);
    
    return () => {
      newSocket.close();
    };
  }, [roomId]);

  const sendMessage = (text: string) => {
    socket?.emit('message', { roomId, text });
  };

  return { messages, sendMessage, isConnected };
}

// components/Chat.tsx
function Chat({ roomId }: { roomId: string }) {
  const { messages, sendMessage, isConnected } = useSocket(roomId);
  const [input, setInput] = useState('');

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage(input);
      setInput('');
    }
  };

  return (
    <div className="chat">
      <div className="status">
        {isConnected ? '🟢 Online' : '🔴 Offline'}
      </div>
      
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className="message">
            {msg.text}
          </div>
        ))}
      </div>
      
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Сообщение..."
        />
        <button type="submit">Отправить</button>
      </form>
    </div>
  );
}

Уведомления в реальном времени

// Серверная часть
io.on('connection', (socket) => {
  const userId = socket.handshake.auth.userId;
  
  // Присоединяем к персональной комнате
  socket.join(`user:${userId}`);
  
  // Отправка уведомления конкретному пользователю
  function sendNotification(targetUserId: string, notification: Notification) {
    io.to(`user:${targetUserId}`).emit('notification', notification);
  }
});

// При создании заказа
async function createOrder(data: OrderData) {
  const order = await prisma.order.create({ data });
  
  // Уведомляем админов
  io.to('admins').emit('new-order', order);
  
  // Уведомляем пользователя
  io.to(`user:${order.userId}`).emit('notification', {
    type: 'order',
    title: 'Заказ создан',
    message: `Заказ #${order.id} успешно создан`
  });
  
  return order;
}

Server-Sent Events (SSE)

SSE проще WebSocket, когда нужен только поток от сервера:

// API route для SSE
// app/api/events/route.ts
export async function GET(request: Request) {
  const encoder = new TextEncoder();
  
  const stream = new ReadableStream({
    start(controller) {
      // Отправляем событие каждые 5 секунд
      const interval = setInterval(() => {
        const data = JSON.stringify({
          time: new Date().toISOString(),
          value: Math.random()
        });
        
        controller.enqueue(
          encoder.encode(`data: ${data}\n\n`)
        );
      }, 5000);
      
      request.signal.addEventListener('abort', () => {
        clearInterval(interval);
        controller.close();
      });
    }
  });
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    }
  });
}

// Клиент
function useLiveData() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const eventSource = new EventSource('/api/events');
    
    eventSource.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };
    
    return () => eventSource.close();
  }, []);
  
  return data;
}

Масштабирование WebSocket

// Используем Redis для синхронизации между серверами
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);

io.adapter(createAdapter(pubClient, subClient));

Заключение

WebSocket и Socket.io — мощные инструменты для real-time функций. Используйте их для чатов, уведомлений, live-обновлений. SSE подходит для простых сценариев с односторонним потоком.

Real-time делает приложение живым. Но помните о масштабировании — WebSocket соединения требуют ресурсов.

WebSocketSocket.ioReal-timeNode.jsЧат

Понравилась статья?

Подпишитесь на наш блог, чтобы не пропустить новые материалы