Что такое Long-Polling, WebSockets, SSE и Comet? Как реализовать на 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 (например, ограничения хостинга), рассмотрите гибридный подход:
- Отправка сообщений: обычный AJAX POST на PHP, который сохраняет сообщение в БД и буферизирует в Redis/Memcached.
- Получение сообщений: 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 одновременных пользователей) этого более чем достаточно. При росте нагрузки — пере
Ответов пока нет.
Войдите чтобы оставить ответ