Ratchet无法向客户端广播数据:单例下$clients数组为空问题
问题分析与解决方案
根本原因
核心问题出在进程隔离:
- WebSocket服务(基于Workerman)是常驻独立进程,
onOpen/onClose中操作的$clients属于这个常驻进程的实例。 - 控制器的HTTP请求运行在PHP-FPM(或其他HTTP服务进程)中,调用
OrderWebSocketServer::getInstance()获取的是HTTP进程内的全新实例,和WebSocket服务的实例完全无关,自然$clients为空。
单例模式仅能保证同一进程内实例唯一,跨进程无法共享对象数据。
解决方案:利用Workerman内部通信实现广播
Workerman原生支持跨进程通信,直接让HTTP进程通知WebSocket进程执行广播即可,这是最简洁可靠的方案。
步骤1:修改WebSocket服务启动文件
给Worker设置唯一名称,方便HTTP进程定位:
use Workerman\Worker; require_once __DIR__ . '/vendor/autoload.php'; // 创建WebSocket Worker $wsWorker = new Worker("websocket://0.0.0.0:8282"); $wsWorker->name = 'OrderWebSocket'; // 关键:设置Worker唯一名称 $wsWorker->count = 4; // 连接建立事件(保留原有逻辑) $wsWorker->onOpen = function($conn) { echo "New connection! ({$conn->resourceId})\n"; }; // 其他事件(onClose/onError)保留原有逻辑... Worker::runAll();
步骤2:修改控制器的广播逻辑
直接通过Workerman的API获取WebSocket Worker实例,调用原生广播方法:
class ObserverHomeRealTimeTestController extends Controller { public function updateStream() { $garageId = Auth::user()->garage_id; $orderQuery = $this->buildOrderQuery($garageId); $pendingOrders = $this->getPendingOrders($orderQuery); $currentOrders = $this->getCurrentOrders($orderQuery); $finishedOrders = $this->getFinishedOrders($orderQuery); $lastObserverAttendance = $this->getLastObserverAttendance(); $data = [ 'observer_attendance' => $lastObserverAttendance->action ?? 'Checkout', 'pending_orders' => HomeResource::collection($pendingOrders)->response()->getData(true), 'current_orders' => HomeResource::collection($currentOrders)->response()->getData(true), 'finished_orders' => HomeResource::collection($finishedOrders)->response()->getData(true), ]; // 关键:获取WebSocket Worker实例并执行广播 $wsWorker = \Workerman\Worker::getWorkerByName('OrderWebSocket'); if ($wsWorker) { $wsWorker->sendMessageToAll(json_encode($data)); } } }
额外优化建议
- 移除
OrderWebSocketServer中的copyClients变量,它对解决问题无帮助,还会造成不必要的内存占用。 onClose中统计连接数建议用$this->clients->count()而非count($this->clients),SplObjectStorage自身的count()方法对连接数统计更准确。
内容的提问来源于stack exchange,提问作者Ahmed kamal
相关产品推荐
相关产品推荐

