Использование HTML5 WebSockets c PHP сервером

Одной из интересных особенностей HTML5 являются WebSockets, которые позволяют нам общаться с сервером без использования запросов AJAX. В этом руководстве мы рассмотрим процесс запуска сервера WebSocket на PHP, а затем создадим клиент для отправки и получения сообщений по протоколу WebSocket.

Что такое веб-сокеты?

WebSocket — это постоянный двунаправленный канал связи между клиентом (например, браузером) и серверной службой. В отличие от соединений HTTP-запрос/ответ, WebSockets могут передавать любое количество протоколов и обеспечивать доставку сообщений от сервера к клиенту без опроса.

Что заменяют веб-сокеты?

Веб-сокеты предназначены для устранения ограничений традиционной связи на основе HTTP. До появления WebSockets HTTP-запросы и ответы не сохраняли состояние, а это означало, что каждая пара запрос/ответ не зависела от предыдущих и последующих запросов/ответов. Из-за этого было очень сложно реализовать связь в реальном времени, когда сервер должен передавать данные клиенту, как только они становятся доступными.

WebSockets также обеспечивают преимущества по сравнению с более старыми технологиями, такими как длительный опрос AJAX и события, отправленные сервером (SSE). WebSockets может легко заменить длительный опрос. Это интересная концепция. Клиент отправляет запрос на сервер, и теперь вместо того, чтобы сервер отвечал данными, которых у него может не быть, он, по сути, держит соединение открытым до тех пор, пока свежие, актуальные данные не будут готовы к отправке. Затем клиент получает это и отправляет другой запрос. Это имеет свои преимущества: одним из них является снижение задержки, поскольку уже открытое соединение не требует установки нового соединения. Однако длительный опрос на самом деле не является частью причудливой технологии: запрос также может истечь по тайм-ауту, и, таким образом, в любом случае потребуется новое соединение.

Многие приложения AJAX используют вышеперечисленное — это часто приводит к неэффективному использованию ресурсов.

Было бы здорово, если бы сервер мог отправлять данные клиентам, готовым их слушать, без какого-либо заранее установленного соединения? Добро пожаловать в мир push-технологий!

В целом, WebSockets обеспечивают более эффективное и действенное решение для связи в реальном времени, и они стали очень популярными для создания веб-приложений в реальном времени.

PHP библиотека веб-сокетов Ratchet

Ratchet — это PHP-библиотека, которая позволяет создавать приложения реального времени, двунаправленные и управляемые событиями с помощью WebSockets. Сегодня мы будем использовать эту библиотеку для реализации сервера WebSockets.

Чтобы установить библиотеку Ratchet WebSockets, вам нужно использовать Composer, который является менеджером зависимостей для PHP. Вот шаги по установке Ratchet с помощью Composer.

$composer require cboden/ratchet

После успешной установки должен быть создан следующий файл composer.json.

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

Итак, мы успешно установили библиотеку Ratchet WebSockets.

Сервер веб-сокетов

В этом разделе мы увидим, как создать сервер WebSockets в PHP.

Создайте файл server.php со следующим содержимым.

<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

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

class WebSocketsServer 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})\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 "Connection {$conn->resourceId} has disconnected\n";
    }
    
    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}

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

Прежде всего, мы определили класс WebSocketsServer, который реализует интерфейс MessageComponentInterface, предоставляющий необходимые функции обратного вызова для обработки событий WebSocket, таких как соединения, сообщения и отключения.

Затем наш код создает новый экземпляр IoServer, который является основной точкой входа для запуска сервера WebSocket. Наконец, он запускает сервер, вызывая метод run() объекта $server.

События веб-сокетов

WebSockets — это протокол, управляемый событиями. Давайте быстро пройдемся по каждому событию, определенному в классе WebSocketsServer.

  • onOpen(ConnectionInterface $conn): срабатывает, когда новый клиент подключается к серверу WebSocket. Параметр $conn содержит объект подключения нового клиента. В этом методе мы добавили подключения к объекту хранения $clients.

  • onMessage(ConnectionInterface $from, $msg): одно из самых важных событий. Он запускается, когда любой клиент отправляет сообщение со стороны клиента на сервер WebSocket. Параметр $from содержит объект подключения клиента, отправившего сообщение, а $msg – это содержимое сообщения. Кроме того, мы перебираем всех клиентов в объекте хранения $clients и отправляем сообщение всем клиентам, кроме клиента-отправителя.

  • onClose(ConnectionInterface $conn): срабатывает, когда любой клиент отключается от сервера WebSocket. Параметр $conn содержит объект подключения клиента. В этом методе мы удалили соединение из объекта хранения $clients.

  • onError(ConnectionInterface $conn, \Exception $e): срабатывает при возникновении ошибки на сервере WebSocket. Параметр $conn содержит объект подключения клиента, где произошла ошибка, а $e — это объект ошибки.

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

Чтобы запустить сервер WebSockets, выполните следующую команду в командной строке.

$php server.php

Теперь наш веб-сервер успешно работает на порту 8089. Вы можете подтвердить это, выполнив следующую команду на другой вкладке, и она должна предоставить вывод, как показано в следующем фрагменте.

$telnet localhost 8089
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Реализация клента

В этом разделе мы создадим HTML-страницу, которая подключается к серверу WebSockets с помощью JavaScript.

Создайте файл client.php со следующим содержимым.

<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<title>WebSockets Client</title>
</head>
<body>
    <div id="wrapper">
        <div id="container">
            <h1>WebSockets Client</h1>
            <div id="chatLog">
            </div><!-- #chatLog -->
            <p id="examples">e.g. try 'hi', 'name', 'age', 'today'</p>
            <input id="text" type="text" />
            <button id="disconnect">Disconnect</button>
        </div><!-- #container -->
    </div>
    <script>
    $(document).ready(function() {
        if (!("WebSocket" in window)) {
            $('#chatLog, input, button, #examples').fadeOut("fast");
            $('<p>Oh no, you need a browser that supports WebSockets. How about <a href="https://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
        } else {
            //Работа с websockets
            connect();

            function connect(){
                var socket;
                var host = "ws://localhost:8089";
                
                try{
                    var socket = new WebSocket(host);
                    message('<p class="event">Socket Status: '+socket.readyState);
                    socket.onopen = function(){
                        message('<p class="event">Socket Status: '+socket.readyState+' (open)');
                    }
                    socket.onmessage = function(msg){
                        message('<p class="message">Received: '+msg.data);
                    }
                    socket.onclose = function(){
                        message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
                    }			
                } catch(exception){
                    message('<p>Error'+exception);
                }
                
                function send(){
                    var text = $('#text').val();
                    if(text==""){
                        message('<p class="warning">Please enter a message');
                        return ;
                    }
                    try{
                        socket.send(text);
                        message('<p class="event">Sent: '+text)
                    } catch(exception){
                        message('<p class="warning">');
                    }
                    $('#text').val("");
                }
                
                function message(msg){
                    $('#chatLog').append(msg+'</p>');
                }
                
                $('#text').keypress(function(event) {
                    if (event.keyCode == '13') {
                        send();
                    }
                });	
                
                $('#disconnect').click(function(){
                    socket.close();
                });
            }
        }
    });
    </script>
</body>
</html>

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

Во-первых, это HTML-структура пользовательского интерфейса WebSocket на стороне клиента.

После этого идет код JavaScript, который создает соединение WebSocket и реализует события WebSockets. Когда документ загружается, он проверяет, поддерживает ли браузер WebSockets. Если это не так, пользователю отображается сообщение с предложением переключиться на браузер, поддерживающий WebSockets, например Google Chrome, Firefox или Edge.

Если браузер поддерживает WebSockets, вызывается функция connect(), которая создает объект WebSocket и подключает его к серверу, работающему на ws://localhost:8089. Функция connect() также определяет прослушиватели событий для объекта WebSocket.

Давайте быстро рассмотрим прослушиватели событий, которые мы определили для объекта WebSocket.

  • onopen: вызывается при создании соединения. Он отправляет сообщение в область чата, чтобы указать, что соединение открыто.
  • onmessage: вызывается при получении сообщения от сервера. Он добавляет полученное сообщение в область чата.
  • onclose: вызывается при закрытии соединения. Он отправляет сообщение в область чата, чтобы указать, что соединение закрыто.

Кроме того, мы также создали несколько вспомогательных функций.

Функция send() вызывается, когда пользователь вводит сообщение и нажимает кнопку Enter. Он отправляет сообщение на сервер с помощью объекта WebSocket и добавляет отправленное сообщение в область чата. Функция message() вызывается для добавления сообщений в область чата. Наконец, кнопка Disconnect вызывает функцию close() объекта WebSocket, чтобы закрыть соединение.

Как тестировать приложение?

Как мы обсуждали ранее, выполните следующую команду в командной строке, чтобы запустить сервер WebSockets.

$php server.php

Затем откройте http://localhost/client.php в нескольких разных вкладках. При загрузке страницы будет открыто соединение WebSocket. Так, например, если вы открыли этот URL-адрес на трех разных вкладках, будет три клиентских соединения WebSocket.

Теперь, когда вы вводите сообщение и нажимаете Enter на одной вкладке, это же сообщение будет транслироваться на другие вкладки, а также сервером WebSockets. Вы можете нажать кнопку «Disconnect», чтобы закрыть соединение WebSocket.

Вот как вы можете использовать WebSockets в PHP с помощью библиотеки Ratchet.