WebSocket em PHP: da teoria às soluções de produção
\n\nO protocolo WebSocket revolucionou o desenvolvimento web, permitindo criar aplicações real-time completas que funcionam sobre uma única conexão TCP. Diferentemente do HTTP clássico, onde o cliente inicia constantemente requisições, o WebSocket estabelece um canal de comunicação bidirecional, pelo qual o servidor pode enviar dados a qualquer momento. Isso é criticamente importante para chats, plataformas de negociação, jogos online e sistemas de monitoramento.
\n\nMuitos desenvolvedores acreditam erroneamente que o PHP não é adequado para tarefas real-time devido à sua natureza síncrona. No entanto, ferramentas modernas, como Ratchet e ReactPHP, mudam completamente essa percepção. Neste artigo, abordaremos técnicas avançadas para criar aplicações WebSocket escaláveis em PHP.
Arquitetura do servidor WebSocket em PHP
\n\nEscolhendo a pilha de tecnologia correta
\n\nPara soluções de produção, bibliotecas simples que funcionam através de socket_create não são adequadas. É necessária uma abordagem orientada a eventos. A pilha recomendada:
- \n
- ReactPHP — event-loop para entrada/saída assíncrona \n
- Ratchet — componente WebSocket sobre ReactPHP \n
- Redis — pub/sub para comunicação entre processos \n
- Supervisor — gerenciador de processos para garantir uptime \n
Servidor básico com Ratchet
\n\nVamos considerar a criação de um servidor WebSocket completo, capaz de lidar com milhares de conexões:
\n\nuse Ratchet\\MessageComponentInterface;\nuse Ratchet\\ConnectionInterface;\nuse React\\EventLoop\\Factory;\nuse React\\Socket\\Server;\nuse Ratchet\\Server\\IoServer;\nuse Ratchet\\Http\\HttpServer;\nuse Ratchet\\WebSocket\\WsServer;\n\nclass ChatServer implements MessageComponentInterface {\n protected $clients;\n protected $redis;\n\n public function __construct() {\n $this->clients = new \\SplObjectStorage;\n $this->redis = new \\Predis\\Client([\n 'scheme' => 'tcp',\n 'host' => '127.0.0.1',\n 'port' => 6379,\n ]);\n // Inscrição no canal Redis para sincronização entre servidores\n $this->redis->subscribe(['chat_messages'], function($redis, $channel, $message) {\n $this->broadcast(json_decode($message, true));\n });\n }\n\n public function onOpen(ConnectionInterface $conn) {\n $this->clients->attach($conn);\n echo "Nova conexão: {$conn->resourceId}\\n";\n }\n\n public function onMessage(ConnectionInterface $from, $msg) {\n $data = json_decode($msg, true);\n \n // Validação da mensagem\n if ($this->validateMessage($data)) {\n // Publicação no Redis para outros servidores\n $this->redis->publish('chat_messages', json_encode($data));\n // Envio para clientes locais\n $this->broadcast($data, $from);\n }\n }\n\n public function onClose(ConnectionInterface $conn) {\n $this->clients->detach($conn);\n echo "Conexão fechada: {$conn->resourceId}\\n";\n }\n\n public function onError(ConnectionInterface $conn, \\Exception $e) {\n echo "Erro: {$e->getMessage()}\\n";\n $conn->close();\n }\n\n protected function broadcast(array $data, ?ConnectionInterface $exclude = null) {\n foreach ($this->clients as $client) {\n if ($exclude === null || $client !== $exclude) {\n $client->send(json_encode($data));\n }\n }\n }\n\n protected function validateMessage(array $data): bool {\n return isset($data['type'], $data['payload']) && \n in_array($data['type'], ['message', 'typing', 'status']);\n }\n}\n\n// Inicialização do servidor\n$loop = Factory::create();\n$webSock = new Server('0.0.0.0:8080', $loop);\n$webServer = n