如何从Ratchet WebSocket获取带用户ID的已连接用户列表?
问题分析
执行test.php时出现端口占用错误,核心原因有两个:
- 你在
test.php中require_once('websocket.php'),而websocket.php末尾会执行$server->run(),尝试再次绑定20081端口,但该端口已被运行中的WebSocket服务器占用,因此抛出EADDRINUSE错误。 - 即使端口不冲突,外部脚本也无法直接访问运行中WebSocket进程的内存对象(
$webSocketServer属于独立进程,和test.php进程内存隔离)。
解决方法
1. 重构WebSocket服务器代码,避免被外部脚本重复启动
修改websocket.php,仅在命令行执行时启动服务器(因为WebSocket服务器本身就是命令行常驻进程):
<?php require_once("/home/bitnami/vendor/autoload.php"); use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; class WebSocketHandler implements MessageComponentInterface { private $clients; // 新增Redis实例,用于存储在线用户(需先安装predis/predis:composer require predis/predis) private $redis; public function __construct() { $this->clients = new \SplObjectStorage(); // 初始化Redis连接 $this->redis = new \Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); } public function onOpen(ConnectionInterface $conn) { $request = $conn->httpRequest; $action = $request->getHeaderLine("action"); if ($action === "addClient") { $userId = $request->getHeaderLine('userId'); $conn->resourceId = $userId; $this->clients->attach($conn, $userId); // 将用户ID存入Redis集合 $this->redis->sAdd('online_users', $userId); file_put_contents('zee1.log', print_r($userId.' '.$action, true)); } } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); // 用户断开时从Redis移除 if (isset($conn->resourceId)) { $this->redis->sRem('online_users', $conn->resourceId); } } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); // 出错时从Redis移除用户 if (isset($conn->resourceId)) { $this->redis->sRem('online_users', $conn->resourceId); } } private function findClientConnection($userId) { foreach ($this->clients as $client) { if ($client->resourceId === $userId) { return $client; } } return null; } public function getClients(){ $connectedClients = []; foreach ($this->clients as $client) { $connectedClients[$client->resourceId] = $client; } return $connectedClients; } } // 仅命令行执行时启动服务器 if (php_sapi_name() === 'cli') { $webSocketServer = new WebSocketHandler(); $server = IoServer::factory( new HttpServer( new WsServer( $webSocketServer ) ), 20081 ); $server->run(); }
2. 修改test.php,通过Redis获取在线用户列表
直接连接Redis读取在线用户集合,无需依赖WebSocket服务器代码:
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); // 连接Redis $redis = new \Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); // 获取在线用户ID列表 $onlineUsers = $redis->sMembers('online_users'); echo "在线用户ID列表:\n"; foreach ($onlineUsers as $userId) { echo "User ID: $userId\n"; } ?>
备选方案:给WebSocket服务器添加HTTP查询接口
如果不想用Redis,可以在WebSocket服务器的事件循环中新增一个HTTP服务,用于外部查询在线用户:
修改websocket.php的服务器启动部分:
if (php_sapi_name() === 'cli') { $webSocketServer = new WebSocketHandler(); // 创建WebSocket服务器 $wsServer = IoServer::factory( new HttpServer( new WsServer( $webSocketServer ) ), 20081 ); // 获取React事件循环 $loop = $wsServer->loop; // 创建HTTP查询服务,监听20082端口 $httpServer = new \React\Http\HttpServer(function (\Psr\Http\Message\ServerRequestInterface $request) use ($webSocketServer) { $clients = $webSocketServer->getClients(); $userIds = array_keys($clients); return new \React\Http\Message\Response( 200, ['Content-Type' => 'application/json'], json_encode(['online_users' => $userIds]) ); }); $httpServer->listen(new \React\Socket\SocketServer('0.0.0.0:20082'), $loop); $wsServer->run(); }
然后test.php通过HTTP请求获取:
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); // 调用HTTP接口获取在线用户 $response = file_get_contents('http://127.0.0.1:20082'); $onlineUsers = json_decode($response, true)['online_users']; echo "在线用户ID列表:\n"; foreach ($onlineUsers as $userId) { echo "User ID: $userId\n"; } ?>
内容的提问来源于stack exchange,提问作者Ritu
相关产品推荐
相关产品推荐

