Long-Polling, WebSockets, SSE и Comet в PHP: полный разбор с кодом

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что скрывается за «живым» обновлением данных? Разбираемся с технологиями реального времени

Вы когда-нибудь задумывались, как работает чат, где сообщения приходят мгновенно, или лента котировок на бирже, которая обновляется сама собой? Классический HTTP-запрос (когда браузер стучится к серверу, а сервер отвечает) тут не подходит — слишком медленно и неэффективно. Нужна магия реального времени.

На Stack Overflow вопрос «What are Long-Polling, Websockets, Server-Sent Events (SSE) and Comet?» набрал тысячи просмотров. Это значит, что путаница в этих терминах — нормальное явление. Сегодня мы разложим всё по полочкам, напишем реальный код на PHP и поймём, когда какую технологию использовать.

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

1. HTTP Polling (Обычный опрос) — дедушка технологий

Прежде чем говорить о продвинутых вещах, давайте вспомним базовый polling. Клиент (JavaScript) просто отправляет запрос на сервер каждые N секунд. Сервер отвечает: «Новых данных нет» или «Вот данные».

Код на PHP (серверная часть):

<?php
// polling_server.php
header('Content-Type: application/json');

// Симулируем получение данных
$data = ['message' => 'Hello at ' . date('H:i:s')];
echo json_encode($data);

Клиентская часть (JavaScript):

setInterval(() => {
    fetch('polling_server.php')
        .then(response => response.json())
        .then(data => console.log(data.message));
}, 3000); // Каждые 3 секунды

Плюсы: простота. Минусы: куча лишних запросов, нагрузка на сервер, задержка до N секунд. Для чата это — смерть.

2. Long-Polling — элегантный обходной путь

Long-Polling — это улучшенная версия polling. Клиент открывает HTTP-запрос, а сервер не отвечает сразу, если данных нет. Он держит соединение открытым (висит в ожидании). Как только данные появляются — сервер отправляет ответ и закрывает соединение. Клиент тут же открывает новый запрос.

С точки зрения клиента — это обычный fetch. С точки зрения сервера — нужно уметь «засыпать» и просыпаться.

Пример Long-Polling на PHP

<?php
// long_polling.php
header('Content-Type: application/json');
header('Cache-Control: no-cache');

// Симулируем ожидание новых сообщений (например, из файла или БД)
$lastCheck = time();
$timeout = 30; // максимум ждём 30 секунд

while (time() - $lastCheck < $timeout) {
    // Проверяем, появились ли новые данные
    $newData = checkForNewMessages(); // ваша функция
    if ($newData !== null) {
        echo json_encode(['status' => 'ok', 'data' => $newData]);
        exit;
    }
    // Спим 1 секунду, чтобы не грузить процессор
    sleep(1);
}

// Если за 30 секунд данных не было — отвечаем пустым ответом
echo json_encode(['status' => 'timeout', 'data' => null]);

Важно: В реальном проекте sleep(1) замените на usleep(500000) (0.5 сек) и используйте неблокирующие проверки (например, filemtime() для файла или SELECT ... FOR UPDATE с таймаутом в БД).

Где используется: старые чаты, уведомления на сайтах без WebSockets. Минус: всё равно много соединений, сервер тратит ресурсы на «висящие» запросы.

3. Server-Sent Events (SSE) — односторонний канал от сервера

SSE (Server-Sent Events) — это технология, встроенная в HTML5. Сервер может отправлять данные клиенту в любой момент, но только в одну сторону (сервер → клиент). Клиент — это обычный EventSource в JavaScript.

SSE идеально подходит для: ленты новостей, котировок, логов, уведомлений.

Код на PHP (сервер):

<?php
// sse_server.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

// Бесконечный цикл отправки событий
while (true) {
    $time = date('H:i:s');
    // Формат SSE: "data: ...\n\n"
    echo "data: Текущее время: {$time}\n\n";
    ob_flush();
    flush();
    sleep(5); // Отправляем каждые 5 секунд
}

Клиентская часть (JavaScript):

const eventSource = new EventSource('sse_server.php');
eventSource.onmessage = function(event) {
    console.log('Новое событие:', event.data);
};
// Можно ловить ошибки
eventSource.onerror = function() {
    console.log('Ошибка соединения SSE');
};

Плюсы SSE: простота (не нужны библиотеки на клиенте), автоматическое переподключение при обрыве, работает через обычный HTTP.

Минусы: только от сервера к клиенту, не поддерживается в IE/старых браузерах (но есть полифилы), ограничение на количество одновременных соединений (браузеры обычно держат 6 на домен).

4. WebSockets — двусторонняя магия

WebSockets — это полноценный двусторонний канал связи. После рукопожатия (через HTTP) соединение «апгрейдится» до протокола WebSocket, и данные летят в обе стороны без накладных расходов HTTP.

Для PHP нам понадобится библиотека, так как нативный PHP не умеет держать тысячи асинхронных соединений. Самый популярный выбор — Ratchet (на базе ReactPHP).

Установка Ratchet (через Composer):

composer require cboden/ratchet

Простой WebSocket-сервер на PHP (chat-server.php):

<?php
require 'vendor/autoload.php';

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "Новое соединение: {$conn->resourceId}\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        // Отправляем сообщение всем клиентам
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        echo "Соединение закрыто: {$conn->resourceId}\n";
    }

    public function onError(ConnectionInterface $conn, \Exception                

Блоги

Рекомендаций по книгам