我正在实现一个聊天室。到目前为止,一切都很好——用户可以通过 JS 客户端从他们的浏览器发送消息,我可以使用 C# 客户端来做同样的事情——这些消息被广播给其他用户。现在,我正在尝试实现“在线用户”。
我的方法如下:
OnConnected
- 将数据库中的用户更新为 IsOnline = trueOnDisconnected
- 如果用户没有任何其他连接,则将数据库中的用户更新为IsOnline = false
- 我将状态存储在数据库中,因为无论如何我都必须在数据库中查询用户缩略图 - 这似乎是在中心使用词典的一种简单替代方法。
我遇到的问题是 OnDisconnected
并不总是为每个客户端 ID 调用 - 陈旧的连接阻止了“如果用户没有任何其他连接”位解析为 true,因此用户始终“在线”。
我能想到的一个 hacky 解决方案是总是在 OnDisconnect
时将用户设置为在数据库中离线 - 但这意味着如果用户打开两个选项卡并关闭一,他们将“离线”。然后,我可以为发送的每条消息将用户重新设置为在线,但这似乎完全是在浪费处理周期,并且仍然会在用户真正在线时将其视为离线的大部分时间。
我相信,如果有一种方法可以保证为每个客户端调用 OnDisconnected,这个问题就会消失。 似乎如果我让客户端打开很长时间(> 10 分钟)然后断开连接,OnDisconnected
永远不会被调用。我会尽力查明重现步骤并保持更新。
那么 - 这是处理在线状态的有效方法吗?如果是这样,还可以做些什么来确保 OnDisconnected
最终为每个连接触发?
这个问题让我很担心,因为如果我没记错的话,现有的连接会随着时间的推移而继续增长,最终会由于未处理的状态连接而溢出。
代码:
我正在使用 In-memory分组方法。
发送消息(C#):
private readonly static ConnectionMapping<string> _chatConnections =
new ConnectionMapping<string>();
public void SendChatMessage(string key, ChatMessageViewModel message) {
message.HtmlContent = _compiler.Transform(message.HtmlContent);
foreach (var connectionId in _chatConnections.GetConnections(key)) {
Clients.Client(connectionId).addChatMessage(JsonConvert.SerializeObject(message).SanitizeData());
}
}
状态管理:
public override Task OnConnected() {
HandleConnection();
return base.OnConnected();
}
public override Task OnDisconnected() {
HandleConnection(true);
return base.OnDisconnected();
}
public override Task OnReconnected() {
HandleConnection();
return base.OnReconnected();
}
private void HandleConnection(bool shouldDisconnect = false) {
if (Context.User == null) return;
var username = Context.User.Identity.Name;
var _userService = new UserService();
var key = username;
if (shouldDisconnect) {
_chatConnections.Remove(key, Context.ConnectionId);
var existingConnections = _chatConnections.GetConnections(key);
// this is the problem - existingConnections occasionally gets to a point where there's always a connection - as if the OnDisconnected() never got called for that client
if (!existingConnections.Any()) { // THIS is the issue - existingConnections sometimes contains connections despite there being no open tabs/clients
// save status serverside
var onlineUserDto = _userService.SetChatStatus(username, false);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, false);
}
} else {
if (!_chatConnections.GetConnections(key).Contains(Context.ConnectionId)) {
_chatConnections.Add(key, Context.ConnectionId);
}
var onlineUserDto = _userService.SetChatStatus(Context.User.Identity.Name, true);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, true);
// broadcast to clients
}
}
连接映射:
public class ConnectionMapping<T> {
private readonly Dictionary<T, HashSet<string>> _connections =
new Dictionary<T, HashSet<string>>();
public int Count {
get {
return _connections.Count;
}
}
public void Add(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
connections = new HashSet<string>();
_connections.Add(key, connections);
}
lock (connections) {
connections.Add(connectionId);
}
}
}
public IEnumerable<string> GetConnections(T key) {
HashSet<string> connections;
if (_connections.TryGetValue(key, out connections)) {
return connections.ToList();
}
return Enumerable.Empty<string>();
}
public void Remove(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
return;
}
lock (connections) {
connections.Remove(connectionId);
if (connections.Count == 0) {
_connections.Remove(key);
}
}
}
}
}
更新
根据 dfowler 的建议,另一种方法是实现数据库内映射而不是内存内映射,这样可以使用更多元数据来识别僵尸化连接。不过,我希望找到内存中问题的解决方案,而不是重新架构 recommended approach那已经实现了。
最佳答案
关于c# - SignalR OnDisconnected - 处理聊天室 "User is Online"的可靠方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21066657/