Представь: ты открываешь чат поддержки на сайте. Пишешь вопрос, и ответ появляется мгновенно — без перезагрузки страницы, без кнопки «обновить». Или смотришь на дашборд с биржевыми котировками, и цифры меняются сами, в реальном времени. Это не магия — это WebSocket или SSE. Разберём, как они работают, чем отличаются и когда что брать.

Почему обычный HTTP тут не справляется

Стандартный HTTP работает по схеме «запрос — ответ». Браузер спрашивает — сервер отвечает — соединение закрывается. Чтобы получить новые данные, нужен новый запрос.

Для статичного контента это нормально. Но если тебе нужно, чтобы сервер сам присылал данные, когда они появятся, — начинаются костыли.

Самый очевидный костыль — polling: браузер каждые N секунд спрашивает сервер «ну что там нового?». Работает, но ужасно неэффективно. При интервале в 1 секунду и тысяче пользователей — это тысяча запросов в секунду, большинство из которых вернут пустой ответ.

Есть чуть умнее — long polling: браузер отправляет запрос, сервер его «подвешивает» до тех пор, пока не появятся новые данные, потом отвечает, и браузер сразу шлёт следующий. Лучше, но всё равно каждый раз нужно устанавливать новое соединение с HTTP-заголовками. Накладные расходы приличные.

Именно для решения этой проблемы появились WebSocket и SSE.

WebSocket — двусторонний канал

WebSocket — это протокол, который создаёт постоянное двустороннее соединение между браузером и сервером. Один раз установили — и обе стороны могут слать данные друг другу в любой момент без лишних HTTP-запросов.

Как это работает

Начинается всё с обычного HTTP-запроса — так называемого handshake. Браузер посылает заголовок Upgrade: websocket, сервер соглашается, и соединение «переключается» на протокол WebSocket. Дальше это уже не HTTP — это постоянный TCP-канал, поверх которого летят фреймы данных.

Пример на JavaScript — проще некуда:

const ws = new WebSocket('wss://example.com/chat');

ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Новое сообщение:', data);
};

ws.onclose = () => {
  console.log('Соединение закрыто');
};

На сервере — примерно так же (Node.js + библиотека ws):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    // Рассылаем всем подключённым
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

Где WebSocket незаменим

  • Чаты и мессенджеры — сообщения должны приходить мгновенно, и пользователь тоже активно отправляет данные.
  • Онлайн-игры — нужна минимальная задержка и постоянный обмен состоянием.
  • Совместное редактирование — Google Docs-подобные приложения, где несколько пользователей правят документ одновременно.
  • Торговые терминалы — котировки, стаканы цен, ордера.
  • Системы мониторинга — метрики в реальном времени с возможностью отправлять команды серверу.

Подводные камни WebSocket

WebSocket — мощный инструмент, но с ним есть нюансы.

Масштабирование — если у тебя несколько серверов, нужно следить, чтобы все сообщения одного чата попадали на один сервер, или использовать брокер (Redis Pub/Sub, например). Иначе пользователи на разных серверах не будут видеть сообщения друг друга.

Прокси и файрволы — некоторые корпоративные сети режут WebSocket-соединения, потому что они выглядят непривычно. Решение — работать через HTTPS/WSS и порт 443.

Переподключение — соединение может упасть. Нужно реализовывать логику переподключения на клиенте, иначе пользователь просто перестанет получать данные и не поймёт почему.

Состояние — сервер должен хранить информацию о всех активных соединениях. Это память, которую нужно учитывать при нагрузке.

SSE — когда данные идут только от сервера

SSE (Server-Sent Events) — это значительно проще. Браузер делает обычный HTTP GET-запрос, сервер принимает его и... не закрывает соединение. Вместо этого он продолжает слать данные — текстовые события в особом формате. Браузер принимает их через стандартный API EventSource.

Важное отличие: SSE — односторонний. Только сервер → браузер. Браузер не может отправить данные через тот же канал.

Формат данных

SSE работает с plain text. Сервер шлёт строки вида:

data: {"price": 3245.50, "symbol": "BTC"}

data: {"price": 3246.10, "symbol": "BTC"}

Каждое событие отделяется пустой строкой. Можно добавлять тип события и ID:

event: price-update
id: 42
data: {"price": 3246.10}

Клиентский код

const source = new EventSource('/api/prices');

source.addEventListener('price-update', (event) => {
  const data = JSON.parse(event.data);
  document.getElementById('price').textContent = data.price;
});

source.onerror = () => {
  console.log('Ошибка соединения, браузер попробует переподключиться');
};

Обрати внимание: переподключение браузер делает автоматически. Это встроено в спецификацию. Если сервер отправлял ID событий, браузер при переподключении пошлёт заголовок Last-Event-ID, и сервер сможет отдать пропущенные события.

Серверная часть (Node.js)

app.get('/api/prices', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const interval = setInterval(() => {
    const price = getLatestPrice();
    res.write(`event: price-update\ndata: ${JSON.stringify(price)}\n\n`);
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
  });
});

Где SSE лучше WebSocket

  • Новостные ленты — обновления прилетают от сервера, пользователь только читает.
  • Уведомления — пришло письмо, обновился статус заказа.
  • Прогресс-бары — загрузка файла, обработка видео, статус задачи.
  • Курсы валют и погода — данные обновляются периодически, пользователь не отправляет ничего назад.
  • Логи в реальном времени — вывод команды или build-лога прямо в браузер.

Прямое сравнение: что когда брать

Параметр WebSocket SSE
Направление Двустороннее Только сервер → клиент
Протокол Собственный (ws/wss) HTTP
Переподключение Нужно писать руками Автоматическое
Поддержка браузеров Отличная Отличная (кроме IE)
Прокси/файрволы Бывают проблемы Работает везде
Формат данных Любой (бинарный тоже) Только текст
Сложность реализации Выше Ниже
Масштабирование Сложнее Проще

Простое правило: если пользователь только получает данные — бери SSE. Если он ещё и отправляет (чат, игра, совместная работа) — WebSocket.

Часто видят такую схему: SSE для уведомлений + обычные HTTP POST-запросы для отправки данных от пользователя. Это вполне рабочая комбинация, не требующая сложной WebSocket-инфраструктуры.

Нагрузка и лимиты

Одно открытое SSE-соединение — это один HTTP-запрос, который висит долго. Браузеры ограничивают количество одновременных соединений к одному домену: в HTTP/1.1 это обычно 6 штук. Если открыть несколько вкладок с SSE — можно упереться в лимит.

Решение — HTTP/2. Там соединения мультиплексируются, и ограничение фактически снимается. Если твой сервер работает на HTTP/2, SSE работает там отлично даже с несколькими вкладками.

WebSocket этой проблемы не имеет — у него собственный протокол, лимиты браузера не применяются.

По нагрузке на сервер: одно WebSocket-соединение потребляет немного памяти (несколько килобайт), но при десятках тысяч пользователей это становится заметным. Node.js традиционно хорош для таких задач за счёт асинхронной модели — один процесс спокойно держит 10-50k соединений. Go и Rust справляются ещё лучше.

Безопасность

Несколько вещей, которые легко забыть:

Аутентификация — WebSocket handshake — это обычный HTTP-запрос, туда можно передать токен в заголовке или query-параметре. Но query-параметры попадают в логи. Лучше — передавать токен в первом сообщении после установки соединения, или использовать cookie (они автоматически идут с handshake-запросом).

CORS — для SSE работают обычные CORS-правила. Для WebSocket — нет, там нужно проверять заголовок Origin на сервере самостоятельно.

Rate limiting — без него злоумышленник может открыть тысячи соединений и положить сервер. Ограничивай количество соединений с одного IP.

Валидация данных — WebSocket позволяет слать что угодно. Не доверяй входящим данным, проверяй формат и типы.

Библиотеки, которые упрощают жизнь

Если не хочется писать всё с нуля — есть готовые решения.

Socket.IO — самая популярная библиотека для WebSocket в Node.js. Сама управляет переподключением, разбивает большие сообщения, поддерживает комнаты и пространства имён. Под капотом использует WebSocket, а если не получается — откатывается на long polling. Минус — добавляет свой протокол поверх WebSocket, поэтому нативный WebSocket-клиент к Socket.IO не подключится.

Pusher, Ably — облачные сервисы. Платишь — получаешь готовую инфраструктуру для реального времени без головной боли с масштабированием. Хорошо для MVP или когда нагрузка непредсказуема.

EventSource polyfill — если нужна поддержка IE (что в 2026 году редкость, но бывает), есть полифиллы.

Пример из практики

Допустим, ты делаешь интернет-магазин. Покупатель оформил заказ — пошла обработка. Хочешь показывать прогресс: «принят → оплачен → передан в доставку → доставлен».

Здесь отлично подойдёт SSE. Сервер держит соединение открытым и шлёт статусы по мере изменений. Браузер обновляет UI. Никакого polling, никакого WebSocket с лишней сложностью — просто один GET-запрос, который живёт несколько минут.

// Клиент
const source = new EventSource(`/api/orders/${orderId}/status`);

source.addEventListener('status', (event) => {
  const { status, label } = JSON.parse(event.data);
  updateOrderStatus(status, label);
  
  if (status === 'delivered') {
    source.close(); // Заказ доставлен — закрываем
  }
});

Мы в REEXY именно так строим статусные страницы для интернет-магазинов — SSE вместо polling. Меньше нагрузки на сервер, чище архитектура, и работает из коробки без дополнительных зависимостей.

Когда это нужно бизнесу

Реальное время — это не только про технический wow-эффект. Это про конверсию и удержание.

По данным исследований, задержка ответа в чате более 10 секунд снижает вероятность покупки в несколько раз. Мгновенные уведомления об изменении статуса заказа снижают нагрузку на службу поддержки — пользователь не звонит, потому что сам видит, что происходит.

Для B2B-систем реальное время критично: трейдеры не могут работать с данными с задержкой, операторы колл-центров должны видеть обновления тикетов мгновенно.

Если тебе нужен сайт или сервис с real-time функциональностью — это задача для профессиональной разработки. Стоимость корпоративного сайта у REEXY начинается от 15 000 ₽, интернет-магазина — от 10 000 ₽, и в эти проекты уже входит нормальная архитектура, а не костыли в виде polling каждые 2 секунды.

Итог

WebSocket и SSE решают одну задачу — данные от сервера к браузеру без лишних запросов. Разница в том, нужна ли двусторонняя связь.

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

Не нужно всегда тянуться к WebSocket — он сложнее и не всегда оправдан. Начни с SSE, и только если понял, что нужна двусторонняя связь, переходи к WebSocket.