PHP Socket存储用户名实现在线用户列表的技术问询
实现WebSocket连接中存储用户名并构建在线用户列表
完全可行!你想在onopen事件发送用户名到服务端并在连接存续期存储的思路非常合理,这是WebSocket场景中关联用户与连接的标准做法之一,能完美解决重复发送用户名、构建在线列表的需求。下面一步步帮你实现:
步骤1:修改前端,在连接建立时仅发送一次用户名
把用户名在WebSocket连接打开的第一时间发送给服务端,后续聊天消息不再携带用户名:
// 初始化WebSocket连接 const websocket = new WebSocket('ws://127.0.0.1:8090'); // 连接成功打开时,发送认证消息(仅一次) websocket.onopen = function(event) { const authMsg = { type: 'auth', name: '<?php echo $_SESSION['pseudo']; ?>' }; websocket.send(JSON.stringify(authMsg)); }; // 发送聊天消息时,不再携带用户名 $('#frmChat').on("submit",function(event){ event.preventDefault(); var messageJSON = { type: 'usrmsg', message: $('#chat-message').val() }; websocket.send(JSON.stringify(messageJSON)); });
步骤2:修改PHP服务端,新增用户名-连接映射
你需要一个额外的数组来存储socket连接和用户名的对应关系,替换原来仅依赖IP的逻辑:
// 全局变量:存储socket资源 -> 用户名的映射(用(int)转成整数作为键更可靠) $clientUsers = array(); // 原有的clients数组保留,存储所有活跃socket连接 $clients = array($socket); // 在接收新连接并完成握手后,先初始化该连接的临时标识(IP) if (in_array($socket, $changed)) { $socket_new = socket_accept($socket); $clients[] = $socket_new; $header = socket_read($socket_new, 1024); perform_handshaking($header, $socket_new, $host, $port); socket_getpeername($socket_new, $ip); // 先把IP作为临时用户名,等待客户端发送真实用户名 $clientUsers[(int)$socket_new] = $ip; // 临时发送IP上线通知,后续会被真实用户名替换 $response = mask(json_encode(array('type'=>'system_user_conn', 'message'=>$ip))); send_message($response); $found_socket = array_search($socket, $changed); unset($changed[$found_socket]); }
步骤3:处理客户端的认证消息,更新用户名映射
在接收消息的逻辑中,优先处理auth类型的消息,替换临时IP为真实用户名:
foreach ($changed as $changed_socket) { while(socket_recv($changed_socket, $buf, 1024, 0) >= 1) { $received_text = unmask($buf); $tst_msg = json_decode($received_text); // 处理认证消息:更新用户名映射 if ($tst_msg->type === 'auth') { $username = $tst_msg->name; $clientUsers[(int)$changed_socket] = $username; // 通知所有用户:真实用户名上线 $response = mask(json_encode(array( 'type'=>'system_user_conn', 'message'=>"{$username} 上线了" ))); send_message($response); // 可选:给新连接的客户端发送当前完整在线用户列表 $onlineUsers = array_values($clientUsers); $userListResp = mask(json_encode(array( 'type'=>'online_list', 'users'=>$onlineUsers ))); socket_write($changed_socket, $userListResp, strlen($userListResp)); break 2; } // 处理普通聊天消息:直接从映射中获取用户名,无需客户端传递 if ($tst_msg->type === 'usrmsg') { $user_name = $clientUsers[(int)$changed_socket]; $user_message = $tst_msg->message; $response_text = mask(json_encode(array( 'type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message ))); send_message($response_text); break 2; } }
步骤4:处理客户端断开,更新在线列表
在断开连接的逻辑中,移除对应的用户名映射,并通知其他用户:
$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ); if ($buf === false) { $found_socket = array_search($changed_socket, $clients); // 获取断开用户的真实用户名 $disconnectedUser = $clientUsers[(int)$changed_socket]; // 从两个数组中移除该连接 unset($clients[$found_socket]); unset($clientUsers[(int)$changed_socket]); // 通知所有用户该用户下线 $response = mask(json_encode(array( 'type'=>'system', 'message'=>"{$disconnectedUser} 断开连接了" ))); send_message($response); // 可选:给所有用户发送更新后的在线列表 $onlineUsers = array_values($clientUsers); $userListResp = mask(json_encode(array( 'type'=>'online_list', 'users'=>$onlineUsers ))); send_message($userListResp); }
可选:前端接收并展示在线列表
如果需要在页面上渲染在线用户列表,可以增加消息监听逻辑:
websocket.onmessage = function(event) { const data = JSON.parse(event.data); switch(data.type) { case 'online_list': // 更新页面上的在线用户列表容器 const userListEl = document.getElementById('online-users'); userListEl.innerHTML = ''; data.users.forEach(user => { const li = document.createElement('li'); li.textContent = user; userListEl.appendChild(li); }); break; case 'system_user_conn': case 'system': // 显示系统通知 console.log(data.message); break; case 'usermsg': // 渲染聊天消息 console.log(`${data.name}: ${data.message}`); break; } };
关键注意事项
- 用
(int)$socket_new作为数组键:因为socket资源是对象,直接作为键会被强制转换为字符串,转成整数更稳定可靠。 - 优先处理认证消息:确保服务端在处理聊天消息前,已经拿到了用户的真实用户名。
- 异常处理:如果客户端未发送认证消息,可以保留IP作为默认标识,或者主动断开未认证的连接。
内容的提问来源于stack exchange,提问作者Feyornowa
相关产品推荐
相关产品推荐

