В этом уроке мы рассмотрим Ratchet , библиотеку PHP для работы с WebSockets. Давайте начнем с определения, что такое WebSockets. MDN говорит:
WebSockets — это передовая технология, позволяющая открыть интерактивный сеанс связи между браузером пользователя и сервером. С помощью этого API вы можете отправлять сообщения на сервер и получать управляемые событиями ответы, не запрашивая ответа у сервера.
WebSockets позволяют нам писать приложения, которые могут передавать данные из браузера на сервер и наоборот в режиме реального времени.
Настроить
Сначала давайте установим Ratchet с помощью Composer:
composer require cboden / ratchet
Сборка приложения
Теперь мы готовы построить приложение. Создайте файл Chat.php
каталоге class/ChatApp
. Это будет класс в пространстве имен ChatApp
, и он будет использовать Ratchet’s MessageComponentInterface
и ConnectionInterface
. MessageComponentInterface
используется в качестве основного строительного блока для приложений Ratchet, а ConnectionInterface
представляет соединение с приложением.
<? php namespace ChatApp ;
use Ratchet \MessageComponentInterface ;
use Ratchet \ConnectionInterface ;
Пусть класс реализует MessageComponentInterface
. Он содержит план для методов, которые нам нужно реализовать, таких как onOpen
, onClose
и onMessage
.
class Chat implements MessageComponentInterface {
}
Внутри класса мы объявляем переменную с именем $clients
. Здесь мы будем хранить список подключенных в данный момент клиентов в нашем приложении чата. Из конструктора вы увидите, что мы используем SplObjectStorage
. Это дает нам возможность хранить объекты. В этом случае объект, который нам нужно сохранить, является объектом подключения для каждого клиента.
protected $clients ;
public function __construct () { $this -> clients = new \SplObjectStorage ;
}
Далее мы реализуем метод onOpen
. Этот метод вызывается каждый раз, когда в браузере открывается новое соединение. Это позволяет сохранить новый объект подключения с помощью метода attach
. Мы также onOpen
что кто-то подключился для проверки правильности работы метода onOpen
.
public function onOpen ( ConnectionInterface $conn ) {
//store the new connection $this -> clients -> attach ( $conn ); echo "someone connected\n" ;
}
Следующим является метод onMessage
. Этот метод вызывается каждый раз, когда сообщение отправляется конкретным клиентом в браузере. Объект подключения клиента, который отправил сообщение, а также фактическое сообщение, передаются в качестве аргумента каждый раз, когда вызывается этот метод. Все, что мы делаем, это перебираем всех подключенных в данный момент клиентов и отправляем им сообщение В приведенном ниже коде мы проверяем, является ли клиент в текущей итерации цикла тем, кто отправил сообщение. Мы не хотим отправлять одно и то же сообщение тому, кто его отправил.
public function onMessage ( ConnectionInterface $from , $msg ) {
//send the message to all the other clients except the one who sent.
foreach ( $this -> clients as $client ) {
if ( $from !== $client ) { $client -> send ( $msg );
}
}
}
Далее onClose
метод onClose
. Как следует из названия, этот метод вызывается каждый раз, когда клиент закрывает соединение WebSocket из браузера. Этот метод запускается, когда пользователь обновляет вкладку браузера или закрывает ее полностью. Все, что нам нужно сделать — это вызвать метод detach
в списке клиентов и передать соединение в качестве аргумента. Это удаляет это конкретное соединение.
public function onClose ( ConnectionInterface $conn ) { $this -> clients -> detach ( $conn ); echo "someone has disconnected" ;
}
Наконец, у нас есть метод onError
который запускается каждый раз, когда возникает ошибка соединения. Когда это происходит, мы выводим произошедшую ошибку и затем вызываем метод close
в соединении, чтобы закрыть ее.
public function onError ( ConnectionInterface $conn , \Exception $e ) { echo "An error has occurred: {$e->getMessage()}\n" ; $conn -> close ();
}
Теперь мы готовы создать файл ввода, который будет использовать файл, который мы только что создали. Мы будем запускать его из командной строки. Назовите его cmd.php
, сохраните его в корне вашего рабочего каталога, затем добавьте следующий код.
<? php require 'vendor/autoload.php' ;
use Ratchet \Server\IoServer ;
use Ratchet \Http\HttpServer ;
use Ratchet \WebSocket\WsServer ;
use ChatApp \Chat ; $server = IoServer :: factory (
new HttpServer (
new WsServer (
new Chat ()
)
),
8080
); $server -> run ();
Этот файл создает новый сервер WebSocket, который работает на порте 8080. Мы подключимся к этому серверу позже со стороны клиента.
Прежде чем мы продолжим, давайте разберем файл. Сначала мы включаем файл автозагрузки, чтобы мы могли использовать различные компоненты Ratchet из нашего файла.
require 'vendor/autoload.php' ;
Далее мы указываем, какие конкретные компоненты Ratchet нам нужны. Для этого чата нам понадобится IoServer
, HttpServer
и WsServer
.
use Ratchet \Server\IoServer ;
use Ratchet \Http\HttpServer ;
use Ratchet \WebSocket\WsServer ;
Вот краткое описание каждого из компонентов:
-
IoServer
позволяет нам получать, читать и записывать и закрывать соединения, а также обрабатывать ошибки, которые мы можем получить. Это также обеспечивает базовую функциональность сервера, поэтому мы можем использовать его для создания нового экземпляра сервера. -
HttpServer
позволяет нам анализировать входящие HTTP-запросы. Этот компонент используется каждый раз, когда пользователь подключается к серверу или пользователь отправляет сообщение. -
WsServer
— это сервер WebSocket. Это позволяет нам общаться с браузерами, которые реализуют API WebSocket. Большинство современных браузеров уже поддерживают WebSockets, поэтому проблем не будет, если вы не планируете поддерживать старые браузеры. Если вы это сделаете, то вы можете взглянуть на добавление политики Flash в ваше приложение.
Возвращаясь к файлу cmd.php
, мы также используем класс Chat
который мы создали ранее.
use ChatApp \Chat ;
Как только это будет сделано, мы можем создать сервер WebSockets. Для этого нам нужно вызвать factory
метод компонента IoServer
а затем передать новый экземпляр HttpServer
. Этот новый экземпляр HttpServer
затем принимает новый экземпляр WsServer
. Наконец, мы передаем новый экземпляр класса Chat
на WsServer
. Вы можете увидеть, какой конкретный компонент сервера включает в себя документацию .
$server = IoServer :: factory (
new HttpServer (
new WsServer (
new Chat ()
)
),
8080
);
Мы запускаем сервер, вызывая метод run
.
$server -> run ();
На этом этапе вы можете запустить сервер из терминала:
php cmd . php
HTML
Далее мы переходим на сторону клиента. Создайте файл index.html
в корне вашего рабочего каталога, а затем добавьте следующий код:
<!DOCTYPE html>
<html lang = "en" >
<head>
<meta charset = "UTF-8" >
<title> chatapp </title>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js" ></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.3/handlebars.min.js" ></script>
<script src = "http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js" ></script>
<link rel = "stylesheet" href = "css/style.css" >
</head>
<body>
<div id = "wrapper" >
<div id = "user-container" >
<label for = "user" > What's your name? </label>
<input type = "text" id = "user" name = "user" >
<button type = "button" id = "join-chat" > Join Chat </button>
</div>
<div id = "main-container" class = "hidden" >
<button type = "button" id = "leave-room" > Leave </button>
<div id = "messages" >
</div>
<div id = "msg-container" >
<input type = "text" id = "msg" name = "msg" >
<button type = "button" id = "send-msg" > Send </button>
</div>
</div>
</div>
<script id = "messages-template" type = "text/x-handlebars-template" >
{{# each messages }}
< div class = "msg" >
< div class = "time" >{{ time }}</ div >
< div class = "details" >
< span class = "user" >{{ user }}< /span>: <span class="text">{{text}}</ span >
</ div >
</ div >
{{/ each }}
</script>
<script src = "js/main.js" ></script>
</body>
</html>
Для клиентской части мы будем использовать jQuery для прослушивания событий щелчка и манипулирования DOM. Handlebars будут обрабатывать шаблоны, а Moment будет отображать время отправки сообщений.
CSS
Для нашей таблицы стилей мы придерживаемся чего-то минимального. Просто минимальный стиль, чтобы он выглядел достаточно прилично. Вот файл css/style.css
:
. hidden { display : none ;
}
#wrapper { width : 800px ; margin : 0 auto ;
}
#leave-room { margin - bottom : 10px ;
float : right ;
}
#user-container { width : 500px ; margin : 0 auto ; text - align : center ;
}
#main-container { width : 500px ; margin : 0 auto ;
}
#messages { height : 300px ; width : 500px ; border : 1px solid #ccc; padding : 20px ; text - align : left ; overflow - y : scroll ;
}
#msg-container { padding : 20px ;
}
#msg { width : 400px ;
}
. user { font - weight : bold ;
}
. msg { margin - bottom : 10px ; overflow : hidden ;
}
. time {
float : right ; color : #939393; font - size : 13px ;
}
. details { margin - top : 20px ;
}
JS
И для нашего файла JavaScript ( js/main.js
) у нас есть следующее:
( function (){
var user ;
var messages = [];
var messages_template = Handlebars . compile ( $ ( '#messages-template' ). html ());
function updateMessages ( msg ){ messages . push ( msg );
var messages_html = messages_template ({ 'messages' : messages }); $ ( '#messages' ). html ( messages_html ); $ ( "#messages" ). animate ({ scrollTop : $ ( '#messages' )[ 0 ]. scrollHeight }, 1000 );
}
var conn = new WebSocket ( 'ws://localhost:8080' ); conn . onopen = function ( e ) { console . log ( "Connection established!" );
}; conn . onmessage = function ( e ) {
var msg = JSON . parse ( e . data ); updateMessages ( msg );
}; $ ( '#join-chat' ). click ( function (){ user = $ ( '#user' ). val (); $ ( '#user-container' ). addClass ( 'hidden' ); $ ( '#main-container' ). removeClass ( 'hidden' );
var msg = {
'user' : user ,
'text' : user + ' entered the room' ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#user' ). val ( '' );
}); $ ( '#send-msg' ). click ( function (){
var text = $ ( '#msg' ). val ();
var msg = {
'user' : user ,
'text' : text ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#msg' ). val ( '' );
}); $ ( '#leave-room' ). click ( function (){
var msg = {
'user' : user ,
'text' : user + ' has left the room' ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#messages' ). html ( '' ); messages = []; $ ( '#main-container' ). addClass ( 'hidden' ); $ ( '#user-container' ). removeClass ( 'hidden' ); conn . close ();
});
})();
Сломать файл JavaScript. Во-первых, мы оборачиваем все в немедленно вызванное выражение функции, чтобы быть уверенными, что включаемый код не будет взаимодействовать с любым другим JavaScript, который мы могли бы включить позже.
( function (){
})();
Инициализируйте пользователя, сообщения и шаблон сообщений. Мы будем использовать переменную user
для хранения имени пользователя, который вошел в чат. messages
предназначена для хранения текущих отправленных сообщений, а messages_template
— это шаблон руля, который мы будем использовать для построения HTML-кода, который показывает все сообщения.
var user ;
var messages = [];
var messages_template = Handlebars . compile ( $ ( '#messages-template' ). html ());
Затем мы создаем метод, который будет выполняться каждый раз, когда сообщение отправляется или получает конкретный пользователь. Что это делает, это помещает сообщение, переданное в качестве аргумента, в массив сообщений. Затем он создает HTML для сообщений, используя массив messages и messages_template
. Затем он обновляет HTML-код контейнера сообщений. Наконец, мы прокручиваем вниз до конца контейнера сообщений, чтобы пользователь видел последнее сообщение.
function updateMessages ( msg ){ messages . push ( msg );
var messages_html = messages_template ({ 'messages' : messages }); $ ( '#messages' ). html ( messages_html ); $ ( "#messages" ). animate ({ scrollTop : $ ( '#messages' )[ 0 ]. scrollHeight }, 1000 );
}
Затем мы создаем новое соединение WebSocket. Он принимает URL-адрес сервера WebSocket в качестве аргумента. В этом случае мы подключаемся к ws://localhost:8080
который является сервером, который мы запускали ранее из терминала.
var conn = new WebSocket ( 'ws://localhost:8080' );
После этого мы прослушиваем событие onopen
в созданном нами соединении WebSocket. Это на самом деле не имеет ничего общего, мы просто используем его, чтобы проверить, успешно ли мы подключились к серверу.
conn . onopen = function ( e ) { console . log ( "Connection established!" );
};
Далее мы слушаем событие onmessage
. Это срабатывает каждый раз, когда новое сообщение отправляется с любого из подключенных клиентов. Когда это событие происходит, мы используем метод JSON.parse
для преобразования строки JSON, полученной с сервера, в объект JavaScript. Затем мы вызываем метод updateMessages
и передаем результат.
conn . onmessage = function ( e ) {
var msg = JSON . parse ( e . data ); updateMessages ( msg );
};
Присоединение к чату
Присоединение к чату происходит, когда пользователь нажимает кнопку «присоединиться к чату». Для этого присваивается значение, введенное пользователем в поле имени пользователя, в переменную пользователя. Затем он скрывает пользовательский контейнер, который содержит поле имени пользователя, и показывает основной контейнер, который содержит сообщения, и поле для ввода нового сообщения. После этого мы создаем новый объект сообщения. Объект сообщения имеет свойства user
, text
и time
. Затем мы вызываем метод updateMessages
чтобы сообщение updateMessages
в окно сообщений. Затем мы вызываем метод send
для соединения WebSocket и передаем строковое представление JSON нового сообщения. Наконец, мы очищаем поле имени пользователя.
$ ( '#join-chat' ). click ( function (){ user = $ ( '#user' ). val (); $ ( '#user-container' ). addClass ( 'hidden' ); $ ( '#main-container' ). removeClass ( 'hidden' );
var msg = {
'user' : user ,
'text' : user + ' entered the room' ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#user' ). val ( '' );
});
Отправка нового сообщения
Здесь мы делаем то же самое, что и когда новый пользователь присоединился к чату, только на этот раз текст будет текстом, введенным пользователем.
$ ( '#send-msg' ). click ( function (){
var text = $ ( '#msg' ). val ();
var msg = {
'user' : user ,
'text' : text ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#msg' ). val ( '' );
});
Выход из комнаты чата
Для этого отправляется сообщение всем подключенным клиентам, что конкретный пользователь покинул комнату. Затем он очищает сообщения HTML и массив. Мы также скрываем основной контейнер и показываем пользовательский контейнер, чтобы новый пользователь мог присоединиться.
$ ( '#leave-room' ). click ( function (){
var msg = {
'user' : user ,
'text' : user + ' has left the room' ,
'time' : moment (). format ( 'hh:mm a' )
}; updateMessages ( msg ); conn . send ( JSON . stringify ( msg )); $ ( '#messages' ). html ( '' ); messages = []; $ ( '#main-container' ). addClass ( 'hidden' ); $ ( '#user-container' ). removeClass ( 'hidden' );
});
Вывод
В этом руководстве мы создали простое приложение для чата, использующее возможности реального времени, предоставляемые WebSockets через Ratchet. Файлы, используемые для этого урока, доступны в этом репозитории Github .
Вопросов? Комментарии? Оставь их ниже!