Построение системы обмена сообщениями на PHP

Раздел: Программирование -> Веб-приложения

Обзор подходов к созданию чата на PHP

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

Как создать чат с использованием MySQL и AJAX polling?

Этот подход подходит для небольших проектов, где не требуется мгновенная доставка сообщений, но нужна простота развёртывания на обычном хостинге с поддержкой PHP и MySQL. Цель: обеспечить хранение сообщений в реляционной базе данных и периодическое обновление списка сообщений на странице без перезагрузки.

Архитектура

Создаются три PHP файла: index.php - основная страница с формой ввода и списком сообщений, send.php - обработчик добавления сообщения, get_messages.php - возвращает новые сообщения в JSON формате. На клиенте используется JavaScript с функцией setInterval для периодического опроса get_messages.php.


<?php
require 'config.php';
$pdo = new PDO('mysql:host='.$host.';dbname='.$dbname.';charset=utf8', $user, $pass);
$stmt = $pdo->query('SELECT * FROM messages ORDER BY id DESC LIMIT 50');
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html>
<head>
    <title>Чат</title>
    <script>
        let lastId = 0;
        function loadMessages() {
            fetch('get_messages.php?last=' + lastId)
                .then(r => r.json())
                .then(data => {
                    if (data.length) {
                        lastId = data[data.length-1].id;
                        // обновить DOM
                    }
                });
        }
        setInterval(loadMessages, 3000);
    </script>
</head>
<body>
    <form id='sendForm'>
        <input type='text' name='message' />
        <button>Отправить</button>
    </form>
    <div id='chat'></div>
</body>
</html>

Php mysql js css (php, mysql, javascript и css)

  • SQL-инъекции: Использовать подготовленные запросы PDO. Никогда не вставлять переменные напрямую в SQL.
  • Потеря сообщений при одновременной записи: Использовать транзакции или блокировку таблицы InnoDB. В большинстве случаев транзакции достаточно.
  • Высокая нагрузка при частых опросах: Увеличить интервал опроса до 5-10 секунд, добавить кэширование на стороне сервера (например, file cache).
  • XSS-атаки: Экранировать вывод сообщений с помощью htmlspecialchars.

Как сделать чат без базы данных, только на файлах?

Цели: минимальные требования - только PHP, никаких внешних СУБД. Подходит для локальных тестов или крошечных проектов. Сообщения хранятся в текстовом файле, каждое сообщение с новой строки, возможно в формате JSON.


<?php
$file = 'chat.txt';
$message = $_POST['message'] ?? '';
if ($message) {
    $line = date('Y-m-d H:i:s') . '|' . htmlspecialchars($message) . PHP_EOL;
    file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
}
$messages = file($file, FILE_IGNORE_NEW_LINES);
?>

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

  • Одновременные запросы: Использовать блокировку LOCK_EX при записи. При чтении блокировка не нужна, но возможно чтение неполной строки.
  • Рост файла: Ограничить количество сообщений, удалять старые. Можно хранить, например, последние 1000 строк.
  • Нет возможности поиска: Файловый чат не подходит для больших объёмов.

Как реализовать чат в реальном времени с помощью WebSocket и Ratchet?

Цели: мгновенная доставка сообщений, низкая задержка, подходит для активной переписки. Требуется запуск отдельного серверного скрипта на основе библиотеки Ratchet. Клиент использует стандартный WebSocket API.


<?php
// Chat.php
namespace MyApp;
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: ' . $conn->resourceId . PHP_EOL;
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        foreach ($this->clients as $client) {
            if ($client !== $from) $client->send($msg);
        }
    }
    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        echo 'Connection ' . $conn->resourceId . ' has disconnected' . PHP_EOL;
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo 'Error: ' . $e->getMessage() . PHP_EOL;
        $conn->close();
    }
}
?>

Index php blog id (блог на php с идентификатором)

  • Требуется shell-доступ: Не работает на большинстве виртуальных хостингов, нужен VPS или выделенный сервер.
  • Сложность настройки: Необходима установка Composer, настройка автозагрузки, открытие порта.
  • Разрывы соединений: Обрабатывать onClose, очищать клиентов.

Как организовать long polling для чата на PHP?

Long polling позволяет серверу удерживать запрос до появления новых данных. Это компромисс между polling и WebSocket. Серверный скрипт зацикливается, проверяет наличие новых сообщений и при появлении отправляет ответ.


<?php
$lastId = $_GET['last'] ?? 0;
$timeout = 30;
$start = time();
while (time() - $start < $timeout) {
    $new = $pdo->query('SELECT * FROM messages WHERE id > ' . intval($lastId) . ' ORDER BY id ASC');
    if ($new->rowCount() > 0) {
        echo json_encode($new->fetchAll(PDO::FETCH_ASSOC));
        exit;
    }
    sleep(1);
}
echo json_encode([]);
?>
  • Удержание соединений: Может исчерпать лимит процессов PHP-FPM. Настроить max_children и таймауты.
  • Сложность с таймаутами: Клиент должен перепосылать запрос после получения ответа или таймаута.
  • Неэффективность на высоких нагрузках: Каждый клиент держит отдельный PHP процесс.

Как использовать Server-Sent Events для односторонней передачи сообщений?

SSE предоставляет стандартный способ отправки событий от сервера к клиенту. Клиент использует EventSource. Сервер отдаёт поток данных с заголовками text/event-stream. Подходит для уведомлений и чатов с односторонним потоком (отправка сообщений через AJAX).


<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$lastId = 0;
while (true) {
    $new = $pdo->query('SELECT * FROM messages WHERE id > ' . $lastId . ' ORDER BY id ASC');
    foreach ($new as $row) {
        echo 'id: ' . $row['id'] . PHP_EOL;
        echo 'data: ' . json_encode($row) . PHP_EOL . PHP_EOL;
        $lastId = $row['id'];
    }
    ob_flush(); flush();
    sleep(2);
}
?>
  • Односторонняя связь: Отправлять сообщения нужно через отдельный AJAX POST.
  • Лимит одновременных соединений: Браузер разрешает не более 6 соединений на домен.
  • Не поддерживается в IE/Edge Legacy: Требуется полифилл.

Полный пример чата на PHP, MySQL и AJAX

Рассмотрим полный набор файлов для чата с базой данных MySQL и AJAX polling. Для работы потребуется сервер с поддержкой PHP 7+ и MySQL.

config.php - настройки подключения к БД.

Пример

<?php
$host = 'localhost';
$dbname = 'chat';
$user = 'root';
$pass = '';
try {
    $pdo = new PDO('mysql:host='.$host.';dbname='.$dbname.';charset=utf8', $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die('Ошибка подключения: ' . $e->getMessage());
}
?>

index.php - главная страница с формой и списком сообщений.

Пример

<?php require 'config.php'; ?>
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>Чат</title>
    <style>
        #chat { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 5px; }
        .msg { margin: 2px 0; }
    </style>
</head>
<body>
    <h3>Чат</h3>
    <div id='chat'>
        <?php
        $stmt = $pdo->query('SELECT id, text, created_at FROM messages ORDER BY id ASC');
        foreach ($stmt as $row) {
            echo '<div class="msg" data-id="' . $row['id'] . '">' . htmlspecialchars($row['text']) . '</div>';
        }
        ?>
    </div>
    <form id='sendForm'>
        <input type='text' id='msgInput' name='message' required />
        <button type='submit'>Отправить</button>
    </form>
    <script>
        let lastId = 0;
        function fetchNew() {
            fetch('get_messages.php?last=' + lastId)
                .then(r => r.json())
                .then(data => {
                    if (data.length) {
                        let chat = document.getElementById('chat');
                        data.forEach(msg => {
                            let div = document.createElement('div');
                            div.className = 'msg';
                            div.textContent = msg.text;
                            div.dataset.id = msg.id;
                            chat.appendChild(div);
                            lastId = msg.id;
                        });
                        chat.scrollTop = chat.scrollHeight;
                    }
                });
        }
        setInterval(fetchNew, 3000);
        document.getElementById('sendForm').onsubmit = function(e) {
            e.preventDefault();
            let input = document.getElementById('msgInput');
            let formData = new FormData();
            formData.append('message', input.value);
            fetch('send.php', { method: 'POST', body: formData })
                .then(() => {
                    input.value = '';
                    fetchNew();
                });
        };
        // Получить начальные сообщения при загрузке
        fetchNew();
    </script>
</body>
</html>

send.php - обработчик отправки сообщения.

Пример

<?php
require 'config.php';
$text = $_POST['message'] ?? '';
if (trim($text) !== '') {
    $stmt = $pdo->prepare('INSERT INTO messages (text, created_at) VALUES (?, NOW())');
    $stmt->execute([htmlspecialchars($text, ENT_QUOTES, 'UTF-8')]);
    echo json_encode(['status' => 'ok']);
} else {
    http_response_code(400);
    echo json_encode(['error' => 'Сообщение не может быть пустым']);
}
?>

get_messages.php - получение новых сообщений.

Пример

<?php
require 'config.php';
$lastId = intval($_GET['last'] ?? 0);
$stmt = $pdo->prepare('SELECT id, text, created_at FROM messages WHERE id > ? ORDER BY id ASC');
$stmt->execute([$lastId]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode($messages);
?>

Результат: при открытии страницы отображаются все существующие сообщения, каждые 3 секунды загружаются новые. Отправка сообщения через форму немедленно обновляет список.

(В браузере отображается окно чата с сообщениями, которые появляются в реальном времени с задержкой до 3 секунд.)

Пример чата на WebSocket с библиотекой Ratchet

Для работы потребуется Composer и доступ к командной строке для запуска сервера.

composer.json

Пример

{
    "require": {
        "cboden/ratchet": "^0.4"
    }
}

server.php - запуск WebSocket сервера на порту 8080.

Пример

<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Chat()
        )
    ),
    8080
);
echo 'Server running on ws://localhost:8080' . PHP_EOL;
$server->run();
?>

Chat.php - класс обработки соединений (полный код приведён в разделе вариантов).

index.html - клиентская страница.

Пример

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>WebSocket Chat</title>
</head>
<body>
    <div id='chat'></div>
    <form id='sendForm'>
        <input type='text' id='msg' />
        <button>Send</button>
    </form>
    <script>
        let ws = new WebSocket('ws://localhost:8080');
        ws.onopen = function() { console.log('Connected'); };
        ws.onmessage = function(e) {
            let msg = JSON.parse(e.data);
            let div = document.getElementById('chat');
            div.innerHTML += '<div>' + msg.text + '</div>';
        };
        document.getElementById('sendForm').onsubmit = function(e) {
            e.preventDefault();
            let input = document.getElementById('msg');
            let msg = { text: input.value };
            ws.send(JSON.stringify(msg));
            input.value = '';
        };
    </script>
</body>
</html>

Результат: при запуске сервера (php server.php) и открытии index.html в браузере сообщения передаются мгновенно всем подключённым клиентам.

(Консоль сервера показывает подключения и отключения. В браузере сообщения появляются сразу после отправки любым клиентом.)

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

En
Chat index php (php)