You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP Ratchet WebSocket客户端无法接收外部脚本消息求助

问题根源与解决方案

核心问题分析

你的代码有两个致命问题:

  1. 进程隔离导致实例不共享:notify.php是独立的PHP进程,和server.php运行的WebSocket服务器进程完全隔离。你在notify.php里新建的ChatServer实例,它的$clients是空的,根本没有已连接的客户端,自然发不出消息。
  2. 数据格式不匹配:前端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>");
    }
});

测试流程

  1. 启动Redis服务器(确保redis-server处于运行状态)
  2. 启动WebSocket服务器:php server.php
  3. 打开chat.html页面,建立WebSocket连接
  4. 执行notify.php:php notify.php
  5. 前端页面会收到系统发送的消息,控制台无报错

内容的提问来源于stack exchange,提问作者madsongr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:00:54