WebSocket in PHP: From Theory to Production Solutions
\n\nThe WebSocket protocol revolutionized web development, enabling the creation of full-fledged real-time applications operating over a single TCP connection. Unlike classic HTTP, where the client constantly initiates requests, WebSocket establishes a two-way communication channel through which the server can send data at any moment. This is critically important for chats, trading platforms, online games, and monitoring systems.
\n\nMany developers mistakenly believe that PHP is unsuitable for real-time tasks due to its synchronous nature. However, modern tools such as Ratchet and ReactPHP completely change this perception. In this article, we will explore advanced techniques for creating scalable WebSocket applications in PHP.
WebSocket Server Architecture in PHP
\n\nChoosing the Right Technology Stack
\n\nSimple libraries operating through socket_create are not suitable for production solutions. An event-driven approach is required. Recommended stack:
- \n
- ReactPHP — event-loop for asynchronous input/output \n
- Ratchet — WebSocket component on top of ReactPHP \n
- Redis — pub/sub for inter-process communication \n
- Supervisor — process manager for ensuring uptime \n
Basic Server with Ratchet
\n\nLet's consider creating a full-fledged WebSocket server capable of handling thousands of connections:
\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 // Subscribe to Redis channel for cross-server synchronization\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 "New connection: {$conn->resourceId}\\n";\n }\n\n public function onMessage(ConnectionInterface $from, $msg) {\n $data = json_decode($msg, true);\n \n // Message validation\n if ($this->validateMessage($data)) {\n // Publish to Redis for other servers\n $this->redis->publish('chat_messages', json_encode($data));\n // Send to local clients\n $this->broadcast($data, $from);\n }\n }\n\n public function onClose(ConnectionInterface $conn) {\n $this->clients->detach($conn);\n echo "Connection closed: {$conn->resourceId}\\n";\n }\n\n public function onError(ConnectionInterface $conn, \\Exception $e) {\n echo "Error: {$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// Start the server\n$loop = Factory::create();\n$webSock = new Server('0.0.0.0:8080', $loop);\n$webServer = n