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