Como criar um servidor WebSocket no PHP com Ratchet para aplicativos em tempo real

October 24, 2019
Escrito por

Como criar um servidor WebSocket no PHP com Ratchet para aplicativos em tempo real

Quando o assunto é WebSocket, o PHP raramente é mencionado devido à sua falta de suporte nativo. Além disso, o Apache, o servidor HTTP em que o PHP normalmente é executado, não foi criado com conexões persistentes em mente, o que força a responsabilidade da implementação em bibliotecas de terceiros.

Embora muitos já tenham tentado falar sobre o PHP no desenvolvimento "em tempo real", a maioria dos argumentos perdeu força em comparação com o projeto Ratchet, que é uma biblioteca WebSocket PHP que exclusivamente envia mensagens bidirecionais em tempo real entre clientes e servidores.

Neste tutorial, usaremos Ratchet com PHP para aprender a criar um servidor WebSocket simples que processa mensagens enviadas de um formulário HTML em tempo real. Nosso formulário exibirá um único <input> e <button> para o envio de mensagens a todos os navegadores do cliente. Sempre que o usuário enviar uma mensagem, ela será exibida em tempo real nas outras telas.

Este aplicativo de exemplo é criado de acordo com o padrão para aplicativos de chat modernos e ajudará você a começar a criar seu próprio aplicativo baseado em WebSocket.

O que são WebSockets?

Os WebSockets são conexões persistentes de baixa latência (ou rápidas) entre um servidor e um ou mais clientes. Ao contrário das solicitações AJAX, os WebSockets são bidirecionais (push-pull), o que significa que tanto o cliente quanto o servidor podem ouvir uns aos outros em tempo real e responder a quaisquer alterações.

Ferramentas necessárias para este tutorial

Neste tutorial, os seguintes pré-requisitos são necessários:

  • PHP 7 ou posterior instalado localmente
  • Composer para o armazenamento das dependências de aplicativos
  • ngrok para criarmos um túnel para o aplicativo do lado do cliente

Criar o diretório de aplicativos e arquivos

No terminal, execute os seguintes comandos para gerar o diretório de projetos e todos os arquivos necessários:

$ mkdir php-sockets && cd php-sockets
$ touch composer.json index.html app.php
$ mkdir app && cd app && touch socket.php

Navegue até o diretório raiz do projeto.

NOTA: caso você queira avançar as etapas, o código-fonte completo está disponível aqui.

Configurar o projeto Composer e incluir o Ratchet

O aplicativo usará o Composer para gerenciar as dependências e fornecer a funcionalidade de carregamento automático. Já criamos o arquivo composer.json necessário para armazenar as dependências do Ratchet. No IDE de sua preferência, abra o arquivo e adicione o seguinte código para incluir o Ratchet.

{
    "autoload": {
        "psr-4": {
            "MyApp\\": "app"
        }
    },
    "require": {
        "cboden/ratchet": "^0.4"
    }
}

Observe que esta declaração usa o protocolo PSR-4 para o carregador automático e mapeia o MyApp para a pasta app que criamos na configuração do projeto. Esse namespace será usado nas etapas subsequentes para incluir as classes de projeto. Conforme exigido por padrão, o arquivo composer.json utiliza a chave require para especificar o pacote Ratchet no projeto.

Agora que o arquivo composer.json está configurado, precisamos instalar as dependências. Para isso, execute o comando a seguir no terminal. Verifique se você está na pasta raiz do projeto.

$ composer install

Criar a classe WebSocket

Agora, chegou a hora de programar! Retorne ao IDE e abra o arquivo app/socket.php. Esse arquivo abrigará a classe necessária para a definição de como as conexões com o servidor WebSocket serão tratadas. Cole o seguinte código conforme mostrado abaixo:

<?php

namespace MyApp;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Socket implements MessageComponentInterface {

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {

        // Store the new connection in $this->clients
        $this->clients->attach($conn);

        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {

        foreach ( $this->clients as $client ) {

            if ( $from->resourceId == $client->resourceId ) {
                continue;
            }

            $client->send( "Client $from->resourceId said $msg" );
        }
    }

    public function onClose(ConnectionInterface $conn) {
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
    }
}

A classe abstrata MessageComponentInterface requer a implementação de todos os quatro métodos, onOpenonMessageonClose e onError, sejam eles utilizados ou não. Aqui está uma breve soma das responsabilidades de cada método:

onOpen: esse método permite responder quando uma nova conexão é estabelecida com o servidor. Pode ser usado para armazenar o ID de conexão em um banco de dados, fazer referência cruzada com outro serviço ou, como neste caso, armazenar a conexão em um conjunto de clientes.

onMessage: esse método provavelmente é a parte mais importante do aplicativo e gerencia a mensagem ou os dados enviados para o servidor. Além de capturar o parâmetro $msg, ele também aceita o $from para que o aplicativo possa decidir o que fazer com base em qual cliente está conectado. Neste exemplo, estamos enviando uma mensagem para cada cliente em tempo real. Também nos certificamos de que não enviamos a mensagem de volta ao cliente que a enviou.

onClose: como o próprio nome já diz, esse método será acionado se uma conexão for fechada pelo cliente.

onError: esse método será acionado se um erro for lançado pela nossa conexão.

Criar o servidor HTTP

Até agora, criamos com sucesso toda a lógica necessária para o processamento das conexões de entrada com o servidor. A última peça que precisamos implementar é o HttpServer que escutará na porta 8080. Abra o arquivo app.php e adicione o seguinte código:

<?php

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Socket;

require dirname( __FILE__ ) . '/vendor/autoload.php';

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Socket()
        )
    ),
    8080
);

$server->run();

Como você pode ver acima, o objeto $server contém o servidor HTTP, gerado por Socket(), e um servidor WebSocket WsServer e é atribuído à porta 8080. Esse arquivo é o que executaremos na linha de comando para ativar o servidor.

Criar o aplicativo cliente

O último item que precisamos criar é o aplicativo de navegador real. Esta página simples conterá <textarea> que captura as tecla pressionadas e as envia ao WebSocket. Dê uma olhada no código abaixo e cole-o no arquivo index.html.

<html>
    <head>
        <style>
            input, button { padding: 10px; }
        </style>
    </head>
    <body>
        <input type="text" id="message" />
        <button onclick="transmitMessage()">Send</button>
        <script>
            // Create a new WebSocket.
            var socket  = new WebSocket('ws://localhost:8080');

            // Define the 
            var message = document.getElementById('message');

            function transmitMessage() {
                socket.send( message.value );
            }

            socket.onmessage = function(e) {
                alert( e.data );
            }
        </script>
    </body>
</html>

O aplicativo do lado do cliente é simples. Ele estabelece uma conexão WebSocket com o servidor em ws://localhost:8080 e, quando o botão "Send" (Enviar) é pressionado, a mensagem é enviada ao servidor. A parte mais interessante é que, uma vez recebida a mensagem, ela é transmitida de volta para cada cliente usando um alert() com o conteúdo da mensagem.

Testar o servidor WebSocket

No seu terminal, execute o seguinte comando para iniciar o servidor WebSocket:

$ php app.php

Em um terminal separado, execute o ngrok para expor o servidor HTTP à Internet.

$ ngrok http 80

tela do ngrok

Copie o endereço HTTPS de encaminhamento e cole-o em duas janelas separadas do navegador. Digite uma mensagem em cada entrada e pressione o botão "Send" (Enviar). Ele acionará o alerta em cada uma das janelas.

GIF testando o funcionamento do websocket

 

Recursos adicionais

Veja o "Tutorial para Websockets PHP que eu gostaria que existisse", criado por Samuel Attard, para saber mais sobre WebSockets PHP.

Conclusão

Estou muito empolgado com o que criamos; você também deve estar! Este tutorial prova que podemos realmente implementar WebSockets no PHP!

Se você já conhece o PHP, sabe que não podemos deixar este script em execução como está: uma vez que a conexão é fechada, o servidor deixará de existir.

Para remover essa limitação, podemos criar um serviço e executar o servidor em segundo plano.

Este artigo foi traduzido do original "How to Create a WebSocket Server in PHP with Ratchet for Real-Time Applications". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.