Представь: ты открываешь чат поддержки на сайте. Пишешь вопрос, и ответ появляется мгновенно — без перезагрузки страницы, без кнопки «обновить». Или смотришь на дашборд с биржевыми котировками, и цифры меняются сами, в реальном времени. Это не магия — это 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.