Long polling vs WebSocket в PHP: что выбрать для realtime-проекта

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

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

Начать курс

Введение: почему ваш сайт «тормозит» в реальном времени?

Представьте, что вы зашли на биржевой сайт, а курс доллара обновляется только после перезагрузки страницы. Или вы отправили сообщение в чате, а собеседник увидел его через 10 секунд. Знакомо? Это — боль любого разработчика, который пытается сделать приложение «живым».

В мире PHP, где каждый запрос живёт свою короткую жизнь, реализовать realtime-функции — задача нетривиальная. Два главных подхода, которые приходят на помощь — Long polling и WebSocket. Какой из них выбрать для вашего проекта на PHP? Давайте разбираться без воды, с живыми примерами кода.

Что такое Long polling в PHP? Долгий запрос с секретом

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

Как это работает на PHP: простой пример

Предположим, у нас есть простой чат. Клиент отправляет AJAX-запрос к PHP-скрипту, который «висит» в цикле, пока не появится новое сообщение в базе данных.

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

$lastMessageId = $_GET['last_id'] ?? 0;
$timeout = 30; // ждём до 30 секунд
$startTime = time();

while (time() - $startTime < $timeout) {
    // Симулируем проверку БД (в реальности - запрос к MySQL/PostgreSQL)
    $newMessages = getNewMessagesFromDB($lastMessageId);
    
    if (!empty($newMessages)) {
        echo json_encode([
            'status' => 'success',
            'messages' => $newMessages
        ]);
        exit;
    }
    
    // Спим 1 секунду, чтобы не убивать CPU
    sleep(1);
}

// Если за 30 секунд ничего не пришло
echo json_encode(['status' => 'timeout', 'messages' => []]);
exit;
?>

На клиенте (JavaScript) это выглядит так:

function startLongPolling(lastId) {
    fetch('/long_polling.php?last_id=' + lastId)
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                data.messages.forEach(msg => addMessageToChat(msg));
                startLongPolling(data.messages[data.messages.length - 1].id);
            } else {
                startLongPolling(lastId); // таймаут — просто перезапускаем
            }
        })
        .catch(() => setTimeout(() => startLongPolling(lastId), 1000));
}

Плюсы Long polling в PHP

  • Простота реализации — не нужны дополнительные сервера, библиотеки. Всё работает на обычном Apache/Nginx + PHP-FPM.
  • Совместимость — работает везде, где есть HTTP. Никаких проблем с прокси, корпоративными файрволами.
  • Низкий порог входа — если вы знаете PHP и AJAX, вы уже можете сделать Long polling.

Минусы Long polling

  • Нагрузка на сервер — каждый «долгий» запрос держит процесс PHP-FPM занятым. При 1000 пользователей вам понадобится 1000 процессов.
  • Задержка (latency) — минимальная задержка = 1 секунда (из-за sleep). Если уменьшить sleep, CPU начнёт плавиться.
  • Сложность масштабирования — Long polling плохо дружит с балансировщиками нагрузки (нужно привязывать пользователя к конкретному серверу).

WebSocket в PHP: настоящий realtime без компромиссов

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

В PHP для работы с WebSocket нужно использовать расширения или библиотеки, так как стандартный PHP-FPM не умеет держать постоянные соединения. Самый популярный вариант — Ratchet (на базе ReactPHP) или Swoole.

Пример WebSocket сервера на PHP с Ratchet

Устанавливаем библиотеку через Composer:

composer require cboden/ratchet

Создаём простой чат-сервер:

<?php
// chat_server.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatServer 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 $e) {
        echo "Ошибка: {$e->getMessage()}\n";
        $conn->close();
    }
}

// Запускаем сервер на порту 8080
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

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

echo "WebSocket сервер запущен на порту 8080...\n";
$server->run();

Запускаем сервер из консоли:

php chat_server.php

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

const ws = new WebSocket('ws://localhost:8080');

ws.onopen = function() {
    console.log('Соединение установлено');
};

ws.onmessage = function(event) {
    const message = event.data;
    addMessageToChat(message);
};

function sendMessage(text) {
    ws.send(text);
}

Плюсы WebSocket для PHP

  • Минимальная задержка — сообщения доставляются мгновенно (миллисекунды).
  • Экономия ресурсов — одно соединение обслуживает один процесс (или поток в Swoole), а не держит процесс PHP-FPM.
  • Двусторонняя связь — сервер сам может инициировать отправку данных без запроса клиента.
  • Низкий трафик — нет HTTP

Блоги

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