SignalR OnDisconnected – 为聊天室处理“用户在线”的可靠方式?

我正在实施一个聊天室。 到目前为止,这么好 – 用户可以通过JS客户端从他们的浏览器发送消息,我可以使用C#客户端做同样的事情 – 这些消息被广播给其他用户。 现在,我正在尝试实施“在线用户”。

我的方法如下:

  • OnConnected – 将数据库中的用户更新为IsOnline = true
  • OnDisconnected – 如果用户没有任何其他连接,请将db中的用户更新为IsOnline = false
  • 我正在将状态存储在数据库中,因为我必须在数据库中查询用户缩略图 – 这似乎是在集线器中使用字典的简单替代方法。

我遇到的问题是OnDisconnected并不总是为每个客户端ID调用 – 过时的连接阻止“如果用户没有任何其他连接”位解析为true,那么用户总是“线上”。

我能想到的一个hacky解决方案是在OnDisconnect始终将用户设置为脱机 – 但这意味着如果用户打开两个选项卡并关闭一个选项卡,它们将“脱机”。 然后,我可以将用户重新设置为联机以获取发送的每条消息,但这似乎完全浪费了处理周期,并且仍然留下了一段时间,当用户真正在线时,用户被视为离线。

我相信如果有办法保证为每个客户端调用OnDisconnected,这个问题就会消失。 好像我让客户端打开很长时间(> 10分钟)然后断开连接, OnDisconnected永远不会被调用。 我会尽力确定repro步骤并保持更新。

那么 – 这是处理在线状态的有效方法吗? 如果是这样,还有什么方法可以确保OnDisconnected最终为每个连接触发?

这个问题让我担心,因为现有的Connections会随着时间的推移而继续增长,如果我没有弄错的话,最终由于未处理的状态连接而溢出。

码:

我正在使用内存中的分组方法。

发送消息(C#):

 private readonly static ConnectionMapping _chatConnections = new ConnectionMapping(); 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 } } 

ConnectionMapping:

 public class ConnectionMapping { private readonly Dictionary<T, HashSet> _connections = new Dictionary<T, HashSet>(); public int Count { get { return _connections.Count; } } public void Add(T key, string connectionId) { lock (_connections) { HashSet connections; if (!_connections.TryGetValue(key, out connections)) { connections = new HashSet(); _connections.Add(key, connections); } lock (connections) { connections.Add(connectionId); } } } public IEnumerable GetConnections(T key) { HashSet connections; if (_connections.TryGetValue(key, out connections)) { return connections.ToList(); } return Enumerable.Empty(); } public void Remove(T key, string connectionId) { lock (_connections) { HashSet connections; if (!_connections.TryGetValue(key, out connections)) { return; } lock (connections) { connections.Remove(connectionId); if (connections.Count == 0) { _connections.Remove(key); } } } } } 

更新

根据dfowler的建议,另一种方法是实现in-db映射而不是in-memory,这样可以使用更多元数据来识别僵尸连接。 我希望能够解决内存中的问题,而不是重新构建一个已经实现的推荐方法 。