Реализация WebSocket-сервера на PHP для приложений реального времени

Традиционно разработчики используют Node.js для работы с WebSockets, но PHP тоже способен эффективно обрабатывать постоянные соединения. С помощью библиотек Ratchet и ReactPHP можно создавать полноценные системы реального времени: чаты, торговые площадки, онлайн-дашборды и collaborative-редакторы. В этой статье разберем архитектурные подходы и практическую реализацию.

Архитектура WebSocket-сервера на PHP

В отличие от HTTP-запросов, WebSocket-соединения остаются открытыми постоянно. Это требует принципиально другого подхода к обработке соединений. PHP, будучи изначально синхронным языком, использует событийный цикл (event loop) через ReactPHP для асинхронной работы.

Ключевые компоненты системы

  • Event Loop — ядро, управляющее вводом-выводом без блокировок
  • Socket Server — принимает TCP-соединения на указанном порту
  • WebSocket Server — преобразует HTTP-запросы в WebSocket-протокол
  • Connection Pool — хранит активные соединения для широковещательной рассылки

Практическая реализация: создаем простой чат

Установите зависимости через Composer: composer require cboden/ratchet react/socket. Базовый сервер состоит из трех основных классов.

Класс обработчика сообщений

Создайте файл Chat.php, который будет обрабатывать подключения и сообщения:

use RatchetMessageComponentInterface;
use RatchetConnectionInterface;

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);
echo "Соединение {$conn->resourceId} закрытоn";
}

public function onError(ConnectionInterface $conn, Exception $e) {
echo "Ошибка: {$e->getMessage()}n";
$conn->close();
}
}

Запуск сервера

Создайте файл server.php для запуска WebSocket-сервера:

use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;
use ReactSocketSocketServer;

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

$chatHandler = new Chat();
$webSocket = new WsServer($chatHandler);
$httpServer = new HttpServer($webSocket);

// Создаем ReactPHP socket сервер
$socket = new SocketServer('0.0.0.0:8080');

$server = new IoServer($httpServer, $socket);
echo "WebSocket сервер запущен на ws://localhost:8080n";
$server->run();

Интеграция с существующим PHP-приложением

Основная сложность — обеспечить взаимодействие между WebSocket-сервером и основной бизнес-логикой приложения. Рассмотрим несколько подходов.

Использование Redis для межпроцессного взаимодействия

Redis Pub/Sub позволяет отправлять сообщения из основного приложения в WebSocket-сервер:

// В основном приложении (например, Laravel Controller)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->publish('chat_channel', json_encode([
'event' => 'new_message',
'data' => ['text' => 'Привет всем!', 'user' => 'Анна']
]));

// В WebSocket-сервере
$loop = Factory::create();
$redisSubscriber = new Client('redis://localhost:6379', $loop);

$redisSubscriber->on('message', function ($channel, $payload) use ($chatHandler) {
$data = json_decode($payload, true);
// Рассылаем сообщение всем подключенным клиентам
});

Аутентификация пользователей

Для идентификации пользователей передавайте токен при установке соединения:

// Клиентская часть JavaScript
const socket = new WebSocket('ws://localhost:8080?token=' + authToken);

// Серверная проверка
public function onOpen(ConnectionInterface $conn) {
$query = $conn->httpRequest->getUri()->getQuery();
parse_str($query, $params);

if (!$this->validateToken($params['token'])) {
$conn->close();
return;
}

$userId = $this->getUserIdFromToken($params['token']);
$conn->userId = $userId;
$this->clients[$userId] = $conn;
}

Оптимизация производительности

WebSocket-сервер на PHP требует особого внимания к ресурсам.

Управление памятью

  • Используйте WeakMap для хранения соединений (доступно с PHP 8.0)
  • Регулярно очищайте неактивные соединения
  • Ограничивайте размер сообщений

// Пример с WeakMap (PHP 8.0+)
private WeakMap $connections;

public function __construct() {
$this->connections = new WeakMap();
}

public function onOpen(ConnectionInterface $conn) {
$this->connections[$conn] = [
'id' => uniqid(),
'last_activity' => time()
];
}

Масштабирование

Для горизонтального масштабирования используйте:

  • Sticky sessions на балансировщике нагрузки
  • Централизованный Redis для синхронизации между инстансами
  • Систему очередей для распределения нагрузки

Практические кейсы применения

1. Система уведомлений в админ-панели

Мгновенное отображение новых заказов, сообщений от пользователей или системных событий без перезагрузки страницы.

2 Collaborative редактирование документов

Синхронизация изменений между пользователями в реальном времени с оперативным преобразованием (Operational Transformation).

3. Торговая площадка с аукционами

Обновление ставок, обратный отсчет и уведомления для всех участников аукциона одновременно.

Ограничения и альтернативы

Хотя PHP с Ratchet отлично подходит для средних нагрузок (до 10,000 одновременных соединений на одном сервере), для экстремальных нагрузок рассмотрите:

  • Socket.io с Node.js для максимальной производительности
  • Pusher или SocketCluster как managed-решения
  • Go или Elixir для собственной высоконагруженной реализации

Главное преимущество PHP-решения — единая кодовая база с основным приложением, упрощенное развертывание и отладка. Для большинства бизнес-приложений с несколькими тысячами одновременных пользователей этого решения более чем достаточно.

Автор: Разработчик