Avatar ·

Что такое Long-Polling, WebSockets, SSE и Comet? Как реализовать на PHP?

Всем привет! Я начинающий PHP-разработчик, пишу простой чат на сайте. Сейчас всё работает так: пользователь отправляет сообщение через AJAX, оно сохраняется в БД, а другие пользователи обновляют страницу вручную (F5), чтобы увидеть новые сообщения. Это ужасно неудобно! Я слышал, что есть технологии для «реального времени»: Long-Polling, WebSockets, SSE и Comet. Но я запутался в терминах. Насколько я понял, Comet — это вообще не технология, а паттерн? Мне нужно, чтобы новые сообщения появлялись у всех пользователей сразу, без перезагрузки страницы. Я использую PHP 8.2, Apache и MySQL. Подскажите, какой подход выбрать для чата? Я пробовал простое решение с setInterval() каждые 2 секунды, но сервер начинает тормозить при 20 пользователях. Вот кусок моего текущего кода (проверка новых сообщений): ```php // check_new_messages.php $lastId = $_GET['last_id']; $stmt = $pdo->prepare("SELECT * FROM messages WHERE id > ? ORDER BY id ASC"); $stmt->execute([$lastId]); $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode($messages); ``` Я понимаю, что это неэффективно. Объясните, пожалуйста, чем отличаются эти подходы и какой лучше для чата на PHP. Заранее спасибо! <<>>

Разбираемся в технологиях реального времени

Вы абсолютно правы: Comet — это не технология, а общее название паттернов, которые позволяют серверу «подталкивать» данные клиенту без постоянных запросов. Long-Polling, WebSockets и SSE — это конкретные реализации этого паттерна. Давайте разберём каждый подход и покажем, как их можно применить в PHP.

1. Long-Polling (Длинный опрос)

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

Реализация на PHP (упрощённо):

// long_poll.php
$lastId = $_GET['last_id'];
$timeout = 30; // ждём максимум 30 секунд
$start = time();

while (true) {
    // Проверяем новые сообщения
    $stmt = $pdo->prepare("SELECT * FROM messages WHERE id > ? ORDER BY id ASC LIMIT 1");
    $stmt->execute([$lastId]);
    $message = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if ($message) {
        echo json_encode($message);
        flush();
        break;
    }
    
    // Проверяем таймаут
    if ((time() - $start) > $timeout) {
        echo json_encode(['timeout' => true]);
        flush();
        break;
    }
    
    // Спим 1 секунду, чтобы не нагружать БД
    usleep(1000000); // 1 секунда
}

Плюсы: работает на любом хостинге, не требует специальных расширений PHP.

Минусы: каждый пользователь держит соединение с PHP-процессом, что быстро исчерпывает лимит одновременных подключений (обычно 100-200 на Apache).

2. Server-Sent Events (SSE)

Клиент подписывается на поток событий от сервера через обычное HTTP-соединение. Сервер может отправлять данные в любой момент. Это односторонний канал: только сервер → клиент.

Реализация на PHP:

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

$lastId = $_GET['last_id'] ?? 0;

while (true) {
    $stmt = $pdo->prepare("SELECT * FROM messages WHERE id > ? ORDER BY id ASC LIMIT 1");
    $stmt->execute([$lastId]);
    $message = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if ($message) {
        echo "id: {$message['id']}\n";
        echo "data: " . json_encode($message) . "\n\n";
        ob_flush();
        flush();
        $lastId = $message['id'];
    }
    
    // Проверяем, не закрыл ли клиент соединение
    if (connection_aborted()) break;
    
    usleep(500000); // 0.5 секунды
}

Плюсы: проще WebSockets, автоматическое переподключение у браузера.

Минусы: только в одну сторону, не поддерживается в IE/старых браузерах.

3. WebSockets

Полноценный двусторонний канал связи. После рукопожатия (handshake) через HTTP, соединение «апгрейдится» до постоянного TCP-соединения. Сервер и клиент могут отправлять данные в любой момент.

Реализация на PHP: Для продакшена используйте библиотеки:

  • Ratchet (http://socketo.me/) — популярная PHP библиотека для WebSockets
  • ReactPHP — асинхронный PHP для событийно-ориентированных приложений
  • Swoole — высокопроизводительный сервер с поддержкой WebSockets

Пример с Ratchet:

// server.php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;

require dirname(__DIR__) . '/vendor/autoload.php';

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Chat()
        )
    ),
    8080
);

$server->run();

// Chat.php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

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);
    }
    
    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

Что выбрать для чата на PHP?

Рекомендую WebSockets через Ratchet или Swoole — это единственный полноценный вариант для двустороннего общения в реальном времени. Но учтите:

  • Вам понадобится выделенный процесс (демон), работающий постоянно
  • Apache не подходит — используйте Nginx как прокси к WebSocket-серверу
  • Для простого чата на общем хостинге — используйте Long-Polling с ограничением времени ожидания

SSE подойдёт, если вам нужно только получать уведомления от сервера (например, новые сообщения), но не отправлять их через то же соединение.

<<>>

Альтернативный подход: гибридное решение

Если вы не можете перейти на WebSockets (например, ограничения хостинга), рассмотрите гибридный подход:

  1. Отправка сообщений: обычный AJAX POST на PHP, который сохраняет сообщение в БД и буферизирует в Redis/Memcached.
  2. Получение сообщений: SSE вместо Long-Polling. SSE использует одно долгое соединение, но в отличие от Long-Polling, не требует постоянных переподключений.

Улучшенная версия SSE с Redis:

// sse_with_redis.php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$lastId = $_GET['last_id'] ?? 0;

while (true) {
    // Подписываемся на канал 'messages'
    $message = $redis->brPop('messages', 5); // ждём до 5 секунд
    
    if ($message) {
        $data = json_decode($message[1], true);
        if ($data['id'] > $lastId) {
            echo "id: {$data['id']}\n";
            echo "data: " . json_encode($data) . "\n\n";
            ob_flush();
            flush();
            $lastId = $data['id'];
        }
    }
    
    if (connection_aborted()) break;
}

Этот подход:

  • Снижает нагрузку на БД (Redis работает в памяти)
  • Не требует постоянных SELECT запросов
  • Работает через обычный Apache/Nginx
  • Легко масштабируется (Redis Pub/Sub поддерживает много подписчиков)

Для маленького проекта (до 50-100 одновременных пользователей) этого более чем достаточно. При росте нагрузки — пере