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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:03:12