Реализация 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-решения — единая кодовая база с основным приложением, упрощенное развертывание и отладка. Для большинства бизнес-приложений с несколькими тысячами одновременных пользователей этого решения более чем достаточно.