Построение real-time коммуникации с PHP

Раздел: PHP программирование -> Чат-системы

Методы организации чата на PHP

Создание чата требует обработки событий в реальном времени. PHP, традиционно работающий по схеме запрос-ответ, может быть адаптирован для этой задачи с помощью различных технологий. Ниже рассматриваются несколько подходов, от простого опроса до WebSocket.

Как сделать чат с помощью периодических AJAX-запросов?

Простой метод: клиент каждые N секунд отправляет запрос к PHP-скрипту, который возвращает новые сообщения. Сервер проверяет время последнего запроса и отдает данные.

// server.php
session_start();
$lastId = $_GET['lastId'] ?? 0;
$messages = getMessagesSince($lastId);
echo json_encode($messages);

создать чат php (создание чата на php)

На клиенте: setInterval(() => fetch(`server.php?lastId=${lastId}`).then(r => r.json()).then(update), 2000);

Проблемы:

  • Высокая нагрузка на сервер при большом числе пользователей.
  • Задержка до 2 секунд.
  • Лишний трафик.

Каким образом реализовать чат без постоянных запросов, но с ожиданием ответа?

Long Polling: клиент отправляет запрос, сервер держит соединение открытым до появления нового сообщения или таймаута. После ответа клиент сразу отправляет новый запрос.

// longpoll.php
$start = time();
while (time() - $start < 30) {
    $new = checkNewMessages();
    if ($new) {
        echo json_encode($new);
        exit;
    }
    usleep(500000); // 0.5 sec
}
echo json_encode([]);

Проблемы: ресурсы на удержание соединений, возможные таймауты.

Как применить Server-Sent Events для чата на PHP?

SSE позволяет серверу отправлять данные клиенту через одно долгое соединение. PHP-скрипт устанавливает заголовки и в цикле отправляет события.

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$lastId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? 0;
while (true) {
    $msgs = getNewMessages($lastId);
    if ($msgs) {
        foreach ($msgs as $m) {
            echo "id: {$m['id']}\n";
            echo "data: " . json_encode($m) . "\n\n";
            $lastId = $m['id'];
        }
    }
    ob_flush(); flush();
    sleep(1);
}

Проблемы: поддержка только односторонней связи, ограничение на количество одновременных соединений.

Каким образом создать двухсторонний чат в реальном времени на PHP с помощью WebSocket?

WebSocket обеспечивает постоянное двустороннее соединение. Для PHP используется библиотека Ratchet. Установка через Composer: composer require cboden/ratchet.

Создаем класс чата:

// Chat.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 "New connection\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 "Closed\n";
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
        echo $e->getMessage();
    }
}

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

// server.php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require 'vendor/autoload.php';

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Chat()
        )
    ),
    8080
);
$server->run();

Клиентский JavaScript:

// client.js
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = function(e) {
    // отобразить сообщение
};
function sendMessage(text) {
    ws.send(text);
}

Пояснения: WebSocket требует отдельного процесса (демона). На сервере должен быть открыт порт 8080, возможны проблемы с брандмауэром или хостингом, поддерживающим только CGI.

Типичные ошибки и их устранение:

  • Ошибка соединения (1006) - часто из-за блокировки порта фаерволом или неправильного пути WebSocket.
  • Сервер не отвечает - убедиться, что процесс Ratchet запущен (php server.php).
  • Проблемы с SSL - для wss требуется SSL-сертификат, настройка через ReactPHP.

Расширенные примеры реализации чата на PHP с WebSocket

Пример 1: Сохранение сообщений в базу данных MySQL

Пример
// ChatDB.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use PDO;

class ChatDB implements MessageComponentInterface {
    private $clients;
    private $db;
    public function __construct() {
        $this->clients = new \SplObjectStorage;
        $this->db = new PDO('mysql:host=localhost;dbname=chat', 'user', 'pass');
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        $data = json_decode($msg, true);
        // сохраняем
        $stmt = $this->db->prepare("INSERT INTO messages (user, text, time) VALUES (?, ?, NOW())");
        $stmt->execute([$data['user'], $data['text']]);
        // рассылаем всем
        foreach ($this->clients as $client) {
            $client->send($msg);
        }
    }
    // ... остальные методы
}

Пример 2: Авторизация через токен при открытии соединения

Пример
// В onOpen проверяем GET-параметр token
public function onOpen(ConnectionInterface $conn) {
    $query = $conn->httpRequest->getUri()->getQuery();
    parse_str($query, $params);
    $token = $params['token'] ?? '';
    if (!validateToken($token)) {
        $conn->close();
        return;
    }
    $this->clients->attach($conn);
}

Клиент подключается: new WebSocket('ws://localhost:8080?token=secret')

Пример 3: Отправка изображений через base64

Пример
// Клиент отправляет JSON: {"type":"image","data":"data:image/png;base64,..."}
// Сервер просто ретранслирует всем, либо сохраняет файл
$data = json_decode($msg, true);
if ($data['type'] === 'image') {
    // можно сохранить файл
    $file = 'uploads/' . uniqid() . '.png';
    file_put_contents($file, base64_decode(explode(',', $data['data'])[1]));
    $data['url'] = $file;
    $msg = json_encode($data);
}
// рассылка

Результат: в чате появляется ссылка на изображение или если отображать сразу через data URI, то прямо в сообщении.

Пример 4: Ограничение истории (последние 50 сообщений)

Пример
// При подключении нового клиента отправляем ему последние 50 сообщений из БД
public function onOpen(ConnectionInterface $conn) {
    // ... авторизация
    $stmt = $this->db->query("SELECT * FROM messages ORDER BY id DESC LIMIT 50");
    $history = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $conn->send(json_encode(['type' => 'history', 'messages' => array_reverse($history)]));
    $this->clients->attach($conn);
}

Пример 5: Использование ReactPHP для масштабирования (неблокирующий ввод-вывод)

Пример
// Замена IoServer на ReactPHP event loop
$loop = React\EventLoop\Factory::create();
$webSock = new React\Socket\Server('0.0.0.0:8080', $loop);
$webServer = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
        new Ratchet\WebSocket\WsServer(
            new Chat()
        )
    ),
    $webSock
);
$loop->run();

Результат: более высокая производительность, возможность интегрировать другие асинхронные задачи.

Создание чата на PHP - comments

En
создать чат php (php)