如何在SignalR中显示已连接(在线)客户端列表及数量
实现SignalR在线用户统计与列表展示
服务器端Hub改造
我们用静态ConcurrentDictionary存储在线用户(多线程环境下保证线程安全),重写OnConnectedAsync和OnDisconnectedAsync处理用户连接与断开,添加方法获取在线用户列表,同时在用户上下线时主动通知前端更新数据。
修改后的ChatHub代码:
using Microsoft.AspNetCore.SignalR; using System.Collections.Concurrent; namespace SignalRChat.Hubs { public class ChatHub : Hub { // 静态字典存储在线用户:Key为ConnectionId,Value为用户名 private static readonly ConcurrentDictionary<string, string> _onlineUsers = new ConcurrentDictionary<string, string>(); public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } // 用户连接时触发 public override async Task OnConnectedAsync() { // 已认证场景:从当前用户身份获取用户名 // 未认证场景:可通过前端传参获取,比如Context.GetHttpContext().Request.Query["user"] var userName = Context.User.Identity?.Name ?? $"匿名用户-{Context.ConnectionId.Substring(0, 8)}"; // 将当前连接用户加入字典 _onlineUsers.TryAdd(Context.ConnectionId, userName); // 通知所有客户端最新在线状态 await Clients.All.SendAsync("UserJoined", userName); await PushOnlineUsersToAll(); await base.OnConnectedAsync(); } // 用户断开连接时触发 public override async Task OnDisconnectedAsync(Exception? exception) { // 从字典移除当前用户 if (_onlineUsers.TryRemove(Context.ConnectionId, out var userName)) { // 通知所有客户端最新在线状态 await Clients.All.SendAsync("UserLeft", userName); await PushOnlineUsersToAll(); } await base.OnDisconnectedAsync(exception); } // 供前端调用获取在线用户数据 public async Task GetOnlineUsers() { var userList = _onlineUsers.Values.ToList(); await Clients.Caller.SendAsync("ReceiveOnlineUsers", userList.Count, userList); } // 私有方法:向所有客户端推送完整在线用户数据 private async Task PushOnlineUsersToAll() { var userList = _onlineUsers.Values.ToList(); await Clients.All.SendAsync("ReceiveOnlineUsers", userList.Count, userList); } } }
前端页面(CSHTML)修改
添加展示在线用户总数和列表的HTML区域:
<div class="online-status"> <p>当前在线用户数:<span id="onlineCount">0</span></p> <h5>在线用户列表</h5> <ul id="userList" class="list-group"></ul> </div> <!-- 保留原有聊天输入区域 --> <div class="input-group mb-3"> <input type="text" id="userInput" class="form-control" placeholder="用户名"> <input type="text" id="messageInput" class="form-control" placeholder="输入消息"> <button class="btn btn-primary" id="sendButton">发送</button> </div> <div id="messagesList"></div>
前端JavaScript逻辑更新
连接Hub后主动获取初始在线用户数据,监听服务器推送的上下线事件,实时更新页面:
"use strict"; var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build(); // 接收在线用户数据并更新页面 connection.on("ReceiveOnlineUsers", function (count, userList) { document.getElementById("onlineCount").textContent = count; var userListElement = document.getElementById("userList"); userListElement.innerHTML = ""; // 清空旧列表 userList.forEach(function (user) { var li = document.createElement("li"); li.className = "list-group-item"; li.textContent = user; userListElement.appendChild(li); }); }); // 可选:用户上线提示 connection.on("UserJoined", function (userName) { alert(userName + " 已上线"); }); // 可选:用户下线提示 connection.on("UserLeft", function (userName) { alert(userName + " 已下线"); }); // 原有接收消息逻辑 connection.on("ReceiveMessage", function (user, message) { var li = document.createElement("li"); li.textContent = `${user}: ${message}`; document.getElementById("messagesList").appendChild(li); }); // 发送消息逻辑 document.getElementById("sendButton").addEventListener("click", function (event) { var user = document.getElementById("userInput").value; var message = document.getElementById("messageInput").value; connection.invoke("SendMessage", user, message).catch(function (err) { return console.error(err.toString()); }); event.preventDefault(); }); // 启动连接后立即获取在线用户数据 connection.start().then(function () { connection.invoke("GetOnlineUsers").catch(function (err) { return console.error(err.toString()); }); }).catch(function (err) { return console.error(err.toString()); });
关键说明
- 内存字典选择:
ConcurrentDictionary适配SignalR多实例场景,静态集合能在多个Hub实例间共享数据,并发操作更安全。 - 用户标识适配:如果无身份认证,可让前端通过QueryString传递用户名,连接地址改为
withUrl("/chatHub?user=" + encodeURIComponent(userName)),再在OnConnectedAsync中通过Context.GetHttpContext().Request.Query["user"]获取。 - 实时更新逻辑:通过
ReceiveOnlineUsers推送完整用户列表,确保页面数据始终准确;UserJoined/UserLeft可做辅助提示。
内容的提问来源于stack exchange,提问作者jvcent
相关产品推荐
相关产品推荐

