rzaripov.kz

WebSocket-сервер на PHP для задач реального времени

Долгоживущие двусторонние соединения давно перестали быть экзотикой и превратились в рабочий инструмент для чатов, push-уведомлений, торговых терминалов и совместных редакторов. Поддержка фреймового обмена в браузере открыла дорогу к интерактивным сценариям без постоянных HTTP-запросов и костылей вроде long polling. На стороне сервера появилась возможность отправлять события клиенту в момент их появления, а не по таймеру опроса.

PHP традиционно воспринимается как язык коротких запросов на генерацию страниц, однако современные библиотеки вроде Ratchet и Workerman позволяют развернуть полноценный WS-сервер за считанные минуты. Преимущество подхода — отсутствие необходимости держать отдельную инфраструктуру на Node.js или Go: вся логика приложения живёт в единой кодовой базе. Далее разобраны архитектура протокола, подготовка окружения, реализация обработчиков, подключение клиента и типовые ошибки при запуске.

Архитектура протокола и отличия от HTTP

Соединение начинается с обычного HTTP-запроса, содержащего заголовок Upgrade: websocket и ключ Sec-WebSocket-Key. Сервер отвечает кодом 101 Switching Protocols, и существующий TCP-канал переходит в режим фреймового обмена. После рукопожатия обе стороны способны отправлять сообщения произвольной длины в текстовом или двоичном формате, не разрывая связь и не тратя время на повторное открытие сокета.

Поддержка keep-alive реализуется через ping/pong фреймы. Если одна сторона не отвечает несколько циклов подряд, соединение закрывается. Это защищает от зависших сокетов, экономит файловые дескрипторы и упрощает балансировку нагрузки. Контроль фрагментации и маскирования данных возложен на транспортный уровень, поэтому разработчик работает уже с готовыми строками либо массивами байт. Версия HyBi 13 совместима со всеми современными браузерами и клиентскими SDK, миграция между сторонними сервисами вроде Pusher или Socket.io проходит без переписывания фронтенда.

Подготовка окружения и выбор библиотеки

Для запуска требуется PHP 8.1 и выше с расширениями sockets и pcntl, доступ к терминалу и свободный порт. Веб-сервер Apache или Nginx на этапе разработки не нужен: WS-сервер слушает соединения самостоятельно. Под Windows удобнее Workerman за счёт более стабильной работы с процессами и сигналами, на Linux выбор между Ratchet и Workerman определяется личными предпочтениями и существующей инфраструктурой.

Установка через Composer укладывается в одну команду: менеджер зависимостей подтягивает все необходимые пакеты и их транзитивные модули. Ratchet построен поверх ReactPHP, поэтому асинхронная модель и событийный цикл достаются разработчику из коробки. Workerman выступает самостоятельным фреймворком без внешних зависимостей, что снижает количество обновлений и потенциальных точек отказа. Для продакшена критичен режим демона: процесс запускается через systemd, supervisord или pm2, логи выносятся в отдельный файл с ротацией, а PID-файл сохраняется в предопределённой директории во избежание ошибок доступа.

Серверная часть на Ratchet

Базовый класс наследуется от MessageComponentInterface и реализует четыре обязательных метода. Конструктор onOpen регистрирует новое соединение и сохраняет ссылку на него в коллекции StorageInterface. Метод onMessage принимает входящие пакеты, выполняет JSON-декодирование и распределяет данные по подписчикам согласно бизнес-логике: широковещание всем, рассылка по каналу или адресная доставка конкретному пользователю.

Метод onClose вызывается при разрыве со стороны клиента или при тайм-ауте. Внутри обязательно удаляется идентификатор из хранилища, иначе память процесса будет расти с каждым подключением и через сутки приведёт к swap-критичной нагрузке. Четвёртый обработчик onError логирует исключения и закрывает проблемный сокет, предотвращая утечку дескрипторов и подвисшие сессии. Запуск сводится к вызову IoServer::factory с указанием хоста и порта, после чего созданный цикл run стартует в режиме блокировки. Поверх поднимается WsServer, который оборачивает исходный компонент и занимается разбором заголовков, фрагментацией и сборкой фреймов.

Клиентское подключение из браузера

Встроенный JavaScript API WebSocket поддерживается всеми актуальными обозревателями без сторонних зависимостей. Достаточно создать экземпляр new WebSocket('wss://example.com:8443'), передав протокол ws для незащищённого канала или wss при работе через TLS. Обработчики onopen, onmessage, onclose и onerror вешаются до первого обмена сообщениями, чтобы не потерять событие успешного рукопожатия либо мгновенной ошибки.

После установления связи клиент отправляет JSON-объект с типом события и полезной нагрузкой. Сервер маршрутизирует пакет по каналам и рассылает участникам соответствующие апдейты. Для передачи крупных блоков данных, например массивов координат или графиков, используется бинарный формат фреймов: он снижает трафик и ускоряет парсинг на мобильных устройствах. Соединение переподключается автоматически благодаря простой обёртке с экспоненциальной задержкой, таймер хранится за пределами React-компонентов и перезапускается при смене статуса. Дополнительно реализуется исходящая очередь сообщений, чтобы не терять команды, отправленные в момент кратковременного обрыва.

Развёртывание, безопасность и типовые ошибки

За прокси Nginx обязательно прописываются заголовки Upgrade и Connection с директивой proxy_http_version 1.1, иначе рукопожатие завершится кодом 400. Тайм-ауты увеличиваются с дефолтных 60 секунд, иначе длинные сессии закрываются преждевременно. TLS реализуется через сертификаты Let's Encrypt, для продакшена wss обязателен: современные браузеры уже не открывают защищённые сайты в смешанном режиме и блокируют активный контент без шифрования.

Аутентификация выполняется при первом сообщении, а не на этапе рукопожатия, поскольку заголовки HTTP-апгрейда проксируются не всегда. Клиент передаёт токен из cookie или localStorage, сервер сверяет подпись и прикрепляет идентификатор пользователя к соединению. Это упрощает горизонтальное масштабирование за балансировщиком, где привязка к конкретному IP бессмысленна. Самые распространённые ошибки связаны с утечками памяти из-за неудалённых соединений, отсутствием защиты от медленных клиентов и превышением лимита размера payload. Защита устанавливается через явное ограничение в коде сервера, периодическую проверку длины очереди и мониторинг количества активных сокетов. Когда базовая версия работает стабильно, появляется пространство для расширения: комнаты, приватные каналы, pub/sub через Redis, кластеризация через Swoole или внешний брокер сообщений — каждое улучшение подключается постепенно без переписывания ядра.