Статьи

Как быстро создать приложение чата с трещоткой

В этом уроке мы рассмотрим Ratchet , библиотеку PHP для работы с WebSockets. Давайте начнем с определения, что такое WebSockets. MDN говорит:

WebSockets — это передовая технология, позволяющая открыть интерактивный сеанс связи между браузером пользователя и сервером. С помощью этого API вы можете отправлять сообщения на сервер и получать управляемые событиями ответы, не запрашивая ответа у сервера.

Connected computers image

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 .

Вопросов? Комментарии? Оставь их ниже!