WebSocket 在 PHP 中的应用:从理论到生产级解决方案
\n\nWebSocket 协议彻底改变了 Web 开发,使得创建基于单个 TCP 连接的全功能实时应用程序成为可能。与经典的 HTTP(客户端不断发起请求)不同,WebSocket 建立了一个双向通信通道,服务器可以随时发送数据。这对于聊天、交易平台、在线游戏和监控系统至关重要。
\n\n许多开发人员错误地认为 PHP 由于其同步特性而不适合实时任务。然而,像 Ratchet 和 ReactPHP 这样的现代工具完全改变了这一看法。在本文中,我们将探讨使用 PHP 创建可扩展 WebSocket 应用程序的高级技术。
PHP 中 WebSocket 服务器的架构
\n\n选择正确的技术栈
\n\n对于生产级解决方案,通过 socket_create 运行的简单库并不适用。需要事件驱动的方法。推荐的技术栈:
- \n
- ReactPHP — 用于异步输入/输出的事件循环 \n
- Ratchet — 基于 ReactPHP 的 WebSocket 组件 \n
- Redis — 用于进程间通信的发布/订阅 \n
- Supervisor — 用于确保正常运行时间的进程管理器 \n
使用 Ratchet 的基础服务器
\n\n让我们考虑创建一个能够处理数千个连接的全功能 WebSocket 服务器:
\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 // 订阅 Redis 频道以实现跨服务器同步\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 "新连接: {$conn->resourceId}\\n";\n }\n\n public function onMessage(ConnectionInterface $from, $msg) {\n $data = json_decode($msg, true);\n \n // 消息验证\n if ($this->validateMessage($data)) {\n // 发布到 Redis 以供其他服务器使用\n $this->redis->publish('chat_messages', json_encode($data));\n // 发送给本地客户端\n $this->broadcast($data, $from);\n }\n }\n\n public function onClose(ConnectionInterface $conn) {\n $this->clients->detach($conn);\n echo "连接已关闭: {$conn->resourceId}\\n";\n }\n\n public function onError(ConnectionInterface $conn, \\Exception $e) {\n echo "错误: {$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// 启动服务器\n$loop = Factory::create();\n$webSock = new Server('0.0.0.0:8080', $loop);\n$webServer = n