如何在Yii2其他控制器中调用WebSocket的commandPing方法?
解决Yii2中从控制器调用WebSocket服务器方法的问题
问题分析
你的WebSocket服务器(ChatServer)是作为独立守护进程运行的,而Web请求的控制器属于另一个进程,直接实例化ChatServer调用方法是无效的——两个进程的内存空间完全隔离,无法直接共享数据或调用方法。必须通过**进程间通信(IPC)**的方式触发WebSocket服务器发送消息。
下面提供基于Redis发布订阅的实现方案,这是当前最常用且可靠的实现方式:
步骤1:安装并配置Redis扩展
首先确保Yii2项目已安装Redis扩展,用于进程间通信:
composer require yiisoft/yii2-redis
在项目配置文件(config/web.php或console.php)中添加Redis组件配置:
'components' => [ 'redis' => [ 'class' => 'yii\redis\Connection', 'hostname' => 'localhost', 'port' => 6379, 'database' => 0, ], // ...其他组件配置 ]
步骤2:修改ChatServer,添加Redis监听与通知逻辑
修改frontend\daemons\ChatServer.php,让服务器启动后监听指定Redis频道,同时抽离独立的通知发送方法:
<?php namespace frontend\daemons; use consik\yii2websocket\events\WSClientEvent; use consik\yii2websocket\WebSocketServer; use Ratchet\ConnectionInterface; use Yii; class ChatServer extends WebSocketServer { private $redisSubscriber; public function init() { parent::init(); $this->on(self::EVENT_CLIENT_CONNECTED, function (WSClientEvent $e) { $e->client->name = null; }); // 初始化Redis订阅监听 $this->initRedisSubscriber(); } private function initRedisSubscriber() { // 订阅传感器通知频道 $this->redisSubscriber = Yii::$app->redis->pSubscribe(['sensor_access_notify'], function($channel, $message) { $data = json_decode($message, true); if ($data) { $this->sendAccessNotification($data['status'], $data['message']); } }); // 加入循环保持订阅监听(非阻塞) $this->loop->addPeriodicTimer(0.1, function() { $this->redisSubscriber->wait(); }); } // 独立的通知发送方法,替代原commandPing public function sendAccessNotification($status, $message) { $res = ['type' => 'access_notify', 'status' => $status, 'message' => $message]; foreach ($this->clients as $chatClient) { $chatClient->send(json_encode($res)); } } protected function getCommand(ConnectionInterface $from, $msg) { $request = json_decode($msg, true); return !empty($request['action']) ? $request['action'] : parent::getCommand($from, $msg); } public function commandPing(ConnectionInterface $client, $msg) { $arr = ["Neo", "Morpheus", "Trinity", "Cypher", "Tank"]; $res = ['type' => 'ping', 'message' => json_encode(array_rand($arr, 1))]; foreach ($this->clients as $chatClient) { $chatClient->send(json_encode($res)); } } }
步骤3:在控制器中触发WebSocket通知
在处理传感器请求的控制器中,通过Redis发布消息,触发WebSocket服务器发送通知:
<?php namespace frontend\controllers; use Yii; use yii\web\Controller; class SensorController extends Controller { public function actionVerifyCard() { // 1. 处理传感器传来的卡片验证逻辑 $cardId = Yii::$app->request->post('card_id'); $hasAccess = $this->checkUserAccess($cardId); // 你的权限验证方法 // 2. 构建通知内容 if ($hasAccess) { $status = 'OK'; $message = '权限验证通过,允许进入'; } else { $status = 'KO'; $message = '权限验证失败,禁止进入'; } // 3. 通过Redis发布消息,触发WebSocket发送通知 Yii::$app->redis->publish('sensor_access_notify', json_encode([ 'status' => $status, 'message' => $message ])); return $this->asJson(['success' => true]); } private function checkUserAccess($cardId) { // 替换为你的实际权限验证逻辑(如查询数据库) return true; } }
步骤4:启动服务与测试
- 确保Redis服务已启动
- 启动WebSocket服务器:
php yii server/start
- 当传感器调用
SensorController::actionVerifyCard接口时,WebSocket服务器会自动接收Redis消息,并向所有连接的客户端发送access_notify类型的通知。
额外说明
- 如果需要向特定客户端发送通知(而非所有客户端),可以在用户连接WebSocket时,将用户ID与连接实例绑定(存储到Redis哈希表),发送时根据用户ID找到对应连接即可。
- 若不想使用Redis,也可通过Yii2消息队列(如RabbitMQ)实现相同逻辑,核心原理一致。
内容的提问来源于stack exchange,提问作者emerald_91
相关产品推荐
相关产品推荐

