Если вы думаете, что PHP — это только генерация HTML и работа с базами данных, то вы сильно недооцениваете этот язык. Вебсокеты — это технология, которая позволяет серверу и клиенту обмениваться данными в реальном времени без постоянных запросов. И PHP здесь не пасёт задних. В этой статье разберём, как подружить PHP с вебсокетами, какие инструменты использовать и напишем простой чат.

Digital-студия WNDER

Что такое вебсокеты и зачем они нужны

Обычный HTTP работает по принципу «запрос-ответ». Клиент спросил — сервер ответил. Если нужно получать обновления, приходится постоянно дёргать сервер. Это как звонить в дверь каждые пять секунд и спрашивать: «Ну что, письмо пришло?». Вебсокеты — это открытый канал связи. Один раз установили соединение — и общаетесь в обе стороны, пока не надоест. Сервер сам может отправить сообщение клиенту, как только что-то произошло. Это идеально для чатов, онлайн-игр, биржевых котировок и совместного редактирования документов.

В PHP долгое время не было встроенной поддержки вебсокетов. Приходилось использовать сторонние библиотеки или писать свой сервер на сокетах. Сейчас ситуация изменилась: есть Ratchet, Swoole, Workerman и другие. Мы рассмотрим Ratchet — простой и популярный вариант.

Как PHP работает с вебсокетами: основные подходы

Есть два основных способа подружить PHP с вебсокетами. Первый — использовать асинхронный сервер на самом PHP. Второй — держать отдельный сервис на Node.js или Go, а PHP только отправлять туда данные. Но мы же хотим всё на PHP? Тогда выбираем Ratchet.

Ratchet — это библиотека, которая реализует протокол WebSocket поверх ReactPHP. Она берёт на себя всю рутину: рукопожатие, фрейминг, управление соединениями. Вам остаётся только написать логику приложения.

Установка через Composer:

composer require cboden/ratchet

Теперь создадим простой сервер, который будет принимать сообщения и рассылать их всем подключённым клиентам. Это основа чата.

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

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

Этот класс реализует четыре метода: onOpen (кто-то подключился), onMessage (пришло сообщение), onClose (отключился), onError (ошибка). В onMessage мы просто пересылаем сообщение всем, кроме отправителя.

Теперь запустим сервер. Создадим файл server.php:

<?php
require __DIR__ . '/vendor/autoload.php';

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

$chat = new Chat();

$webSocket = new WsServer($chat);
$httpServer = new HttpServer($webSocket);

$server = IoServer::factory(
    $httpServer,
    8080
);

$server->run();

Запускаем в консоли:

php server.php

Сервер слушает порт 8080. Теперь нужно написать клиентскую часть на JavaScript, которая подключится к этому серверу.

const conn = new WebSocket('ws://localhost:8080');

conn.onopen = function(e) {
    console.log('Соединение установлено');
};

conn.onmessage = function(e) {
    console.log('Получено: ' + e.data);
};

conn.onclose = function(e) {
    console.log('Соединение закрыто');
};

// Отправка сообщения
function sendMessage(message) {
    conn.send(message);
}

Это минимальный рабочий пример. Но в реальном проекте нужно больше: аутентификация, приватные комнаты, хранение истории. Всё это можно реализовать на PHP.

Библиотеки и инструменты для работы с вебсокетами на PHP

Кроме Ratchet, есть и другие решения. Сравним их в таблице.

Библиотека Особенности Сложность
Ratchet Простой старт, хорошая документация, основан на ReactPHP Низкая
Swoole Высокая производительность, асинхронность, но требует установки расширения Средняя
Workerman Быстрый, поддерживает много протоколов, есть готовые решения Средняя
ReactPHP Низкоуровневый, гибкий, но нужно много писать самому Высокая

Для большинства задач Ratchet — оптимальный выбор. Он не требует установки дополнительных расширений (кроме самого PHP), легко ставится через Composer и имеет понятный API.

Практические советы и лайфхаки

Вот несколько вещей, которые сэкономят вам время и нервы.

Правило: Всегда запускайте WebSocket-сервер через supervisor или systemd. Если он упадёт, соединения оборвутся, и клиенты не смогут переподключиться автоматически. Автозапуск спасёт ситуацию.

Дальше — несколько советов по коду.

  • Используйте хранилище для соединений. SplObjectStorage — это хорошо, но если сервер перезапустится, все клиенты потеряются. Для продакшена лучше хранить состояние в Redis или Memcached.
  • Обрабатывайте разрывы. Клиент может отключиться в любой момент. Всегда очищайте ресурсы в onClose.
  • Не блокируйте сервер. Если в onMessage вы делаете тяжёлый запрос к базе, остальные клиенты ждут. Используйте асинхронные вызовы или выносите логику в отдельные процессы.
  • Логируйте всё. Вебсокеты — это постоянный поток данных. Без логов вы не поймёте, что пошло не так.
  • Тестируйте под нагрузкой. Один клиент — это не показатель. Используйте инструменты вроде Artillery или написанные на PHP скрипты для эмуляции сотен соединений.

Ещё один момент: безопасность. WebSocket не имеет встроенной защиты от CSRF или XSS. Вы должны сами проверять входящие данные и аутентифицировать пользователей. Например, можно передавать токен при подключении и проверять его на сервере.

public function onOpen(ConnectionInterface $conn) {
    $query = $conn->httpRequest->getUri()->getQuery();
    parse_str($query, $params);
    if (!isset($params['token']) || !validateToken($params['token'])) {
        $conn->close();
        return;
    }
    $this->clients->attach($conn);
}

Так вы отсеете левые подключения.

Заключение

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

Попробуйте написать свой чат на основе примеров выше. Это лучший способ разобраться. А если нужна помощь — обращайтесь в Digital-студию WNDER, мы поможем с любым проектом.

Студия WNDER