Веб-сокеты в PHP: как заставить сайт говорить в реальном времени
Представьте себе обычный сайт. Вы нажали кнопку — и ждёте, пока обновится страница. Это как отправлять письмо почтой: выбросил в ящик и надеешься, что дойдёт. А теперь представьте, что вы общаетесь по телефону — сразу слышите собеседника. WebSockets — это именно такой «телефон» для веба. И да, на PHP это работает.
Многие считают, что WebSocket — это удел Node.js или Python. Но PHP, хоть и был создан для «склеивания» HTML, давно перерос свою колыбель. Сегодня мы разберём, как сделать real-time приложение на PHP — будь то чат, лента уведомлений или живая аналитика. И никаких костылей.
Если вы когда-нибудь пытались сделать live чат на PHP через long polling и видели, как сервер падает под 1000 запросов в секунду — эта статья для вас. WebSockets меняют правила игры.
Что такое WebSocket и почему PHP с ним дружит?
WebSocket — это двусторонний канал связи между браузером и сервером. В отличие от HTTP, где клиент всегда инициирует запрос, здесь сервер может сам отправить данные клиенту. Это даёт настоящее реальное время, а не его имитацию.
Ключевое отличие от long polling (когда клиент постоянно спрашивает «ну что, есть новости?») — в WebSocket соединение держится открытым. Один раз установили — и общаетесь без перерыва. Экономия трафика и ресурсов колоссальная.
PHP, как язык, выполняет скрипты последовательно. Но это не мешает ему быть отличным сервером для WebSocket. Секрет в архитектуре: мы не запускаем PHP через веб-сервер (Apache/Nginx) для каждого соединения, а используем его как самостоятельный демон. И тут на сцену выходят библиотеки.
Библиотеки для WebSocket на PHP: что выбрать?
Самых популярных библиотек две — и обе заслуживают внимания:
- Ratchet — дедушка PHP WebSocket. Надёжный, проверенный, но немного «старорежимный». Использует событийную модель на основе
ReactPHP. Отлично подходит для простых чатов и уведомлений. - Swoole — зверь. Это не просто библиотека, а целый движок, который переписывает ядро PHP. Даёт асинхронность, корутины и производительность, близкую к Go. Но требует установки расширения на сервере.
Для первого знакомства я рекомендую Ratchet — он проще в освоении и не требует правки php.ini. Плюс, он прекрасно работает в связке с ReactPHP, который мы тоже сегодня затронем.
Пишем простой WebSocket-сервер на PHP с Ratchet
Давайте создадим базовый эхо-сервер. Он будет принимать сообщения от клиента и отправлять их обратно. Это классический «Hello, World» для WebSocket.
Сначала установим Ratchet через Composer. Откройте терминал в корне проекта:
composer require cboden/ratchet
Теперь создадим файл server.php:
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class EchoServer implements MessageComponentInterface {
public function onOpen(ConnectionInterface $conn) {
echo "Новое соединение: {$conn->resourceId}\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
echo "Получено: {$msg}\n";
$from->send("Эхо: {$msg}");
}
public function onClose(ConnectionInterface $conn) {
echo "Соединение {$conn->resourceId} закрыто\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "Ошибка: {$e->getMessage()}\n";
$conn->close();
}
}
// Запускаем сервер
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new EchoServer()
)
),
8080
);
echo "WebSocket сервер запущен на порту 8080...\n";
$server->run();
Запустите сервер командой:
php server.php
Теперь откройте консоль браузера (F12) и введите:
let ws = new WebSocket('ws://localhost:8080');
ws.onmessage = e => console.log(e.data);
ws.send('Привет, WebSocket!');
Вы увидите в консоли: Эхо: Привет, WebSocket!. Поздравляю — вы только что сделали real-time приложение на PHP!
Сложный пример: чат-комната с историей
Давайте усложним задачу. Сделаем live чат на PHP, где все видят сообщения друг друга, а новые участники получают историю последних 10 сообщений.
Создадим новый класс ChatServer.php:
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface {
protected $clients;
protected $history = [];
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "Новый пользователь. Всего: {$this->clients->count()}\n";
// Отправляем историю новому участнику
$history = array_slice($this->history, -10);
if (!empty($history)) {
$conn->send(json_encode([
'type' => 'history',
'messages' => $history
]));
}
// Уведомляем всех о новом участнике
$this->broadcast(json_encode([
'type' => 'system',
'message' => 'Пользователь #' . $conn->resourceId . ' присоединился'
]), $conn);
}
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg, true);
if (!$data || !isset($data['message'])) return;
$message = [
'type' => 'message',
'user' => 'Пользователь #' . $from->resourceId,
'text' => htmlspecialchars($data['message']),
'time' => date('H:i:s')
];
// Сохраняем в истории
$this->history[] = $message;
// Отправляем всем
$this->broadcast(json_encode($message), $from);
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
$this->broadcast(json_encode([
'type' => 'system',
'message' => 'Пользователь #' . $