PHP Ratchet WebSocket客户端无法接收外部脚本消息求助
问题根源与解决方案
核心问题分析
你的代码有两个致命问题:
- 进程隔离导致实例不共享:
notify.php是独立的PHP进程,和server.php运行的WebSocket服务器进程完全隔离。你在notify.php里新建的ChatServer实例,它的$clients是空的,根本没有已连接的客户端,自然发不出消息。 - 数据格式不匹配:前端
message事件里直接对收到的内容做JSON.parse,但notify.php发送的是纯文本,会触发解析错误,导致前端代码中断,就算消息真的发过来也不会显示。
解决方案:用Redis实现进程间通信
要让外部脚本给WebSocket客户端发消息,必须通过进程间通信机制。最简便可靠的方式是用Redis的发布/订阅功能,让WebSocket服务器订阅Redis频道,外部脚本向频道发消息,服务器收到后转发给所有客户端。
步骤1:安装依赖
先安装ReactPHP的Redis组件和PHP Redis扩展:
composer require react/redis
确保你的PHP环境已经安装php-redis扩展。
步骤2:修改ChatServer类,集成Redis订阅
修改后的ChatServer会在启动时订阅Redis频道,收到频道消息后自动转发给所有客户端:
<?php namespace MyApp; use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; use React\EventLoop\LoopInterface; use React\Redis\Factory; require('../vendor/autoload.php'); class ChatServer implements MessageComponentInterface { protected $clients; protected $loop; public function __construct(LoopInterface $loop) { $this->clients = new \SplObjectStorage; $this->loop = $loop; $this->setupRedisSubscription(); } // 初始化Redis订阅 private function setupRedisSubscription() { $factory = new Factory($this->loop); $factory->createClient('redis://localhost:6379')->then(function ($redis) { // 订阅指定频道 $redis->subscribe('chat_notifications', function ($channel, $message) { $this->sendMessageToAll($message); }); }); } public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); // 发送符合前端格式的JSON消息 $msg = json_encode([ 'name' => 'Server', 'message' => 'Connection established!' ]); echo $msg . "\n"; $this->sendMessageToAll($msg); } public function onMessage(ConnectionInterface $from, $msg) { foreach ($this->clients as $client) { $client->send($msg); } } public function sendMessageToAll($msg) { foreach ($this->clients as $client) { echo "Sending message to {$client->resourceId} \n"; $client->send($msg); } } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } }
步骤3:修改server.php,传入事件循环
需要把React的事件循环传给ChatServer,让它能初始化Redis订阅:
use MyApp\ChatServer; use Ratchet\App; require('../vendor/autoload.php'); require('./chat.php'); $app = new App('localhost', 8080); // 获取React事件循环实例 $loop = $app->getLoop(); // 传入循环给ChatServer $app->route('/chat', new ChatServer($loop), array('*')); $app->route('/echo', new Ratchet\Server\EchoServer, array('*')); $app->run();
步骤4:修改notify.php,通过Redis发布消息
现在notify.php不需要创建ChatServer实例,直接向Redis频道发布消息即可:
<?php // 连接Redis $redis = new Redis(); $redis->connect('localhost', 6379); // 构造符合前端格式的JSON消息 $message = json_encode([ 'name' => 'System', 'message' => 'sending message from registerEvent' ]); echo $message . "\n"; // 发布到订阅频道 $redis->publish('chat_notifications', $message);
步骤5:修复前端错误处理
给前端增加JSON解析的错误捕获,避免纯文本消息导致代码中断:
socket.addEventListener('message', function (event) { console.log(event.data); let data; try { data = JSON.parse(event.data); chat.insertAdjacentHTML('beforeend', "<p><b>" + data.name + " says: </b>" + data.message + "</p>"); } catch (e) { // 处理非JSON格式的消息 chat.insertAdjacentHTML('beforeend', "<p><b>System says: </b>" + event.data + "</p>"); } });
测试流程
- 启动Redis服务器(确保
redis-server处于运行状态) - 启动WebSocket服务器:
php server.php - 打开
chat.html页面,建立WebSocket连接 - 执行
notify.php:php notify.php - 前端页面会收到系统发送的消息,控制台无报错
内容的提问来源于stack exchange,提问作者madsongr
相关产品推荐
相关产品推荐

