C# 信号机断开-处理“故障”的可靠方法;“用户在线”;聊天室?

C# 信号机断开-处理“故障”的可靠方法;“用户在线”;聊天室?,c#,signalr,signalr-hub,signalr.client,C#,Signalr,Signalr Hub,Signalr.client,我正在实现一个聊天室。到目前为止,还不错——用户可以通过JS客户端从浏览器发送消息,我也可以使用C#客户端做同样的事情——这些消息可以广播给其他用户。现在,我正在尝试实现“在线用户” 我的做法如下: OnConnected-将数据库中的用户更新为IsOnline=true OnDisconnected-如果用户没有任何其他连接,请将数据库中的用户更新为IsOnline=false 我将状态存储在数据库中,因为我必须查询数据库中的用户缩略图——这似乎是在中心使用字典的简单替代方法 我遇到的问题

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

我的做法如下:

  • OnConnected
    -将数据库中的用户更新为IsOnline=true
  • OnDisconnected
    -如果用户没有任何其他连接,请将数据库中的用户更新为
    IsOnline=false
  • 我将状态存储在数据库中,因为我必须查询数据库中的用户缩略图——这似乎是在中心使用字典的简单替代方法
我遇到的问题是,
OnDisconnected
并不总是为每个客户端Id调用-陈旧的连接阻止“如果用户没有任何其他连接”位解析为true,因此用户总是“在线”

我能想到的一个黑客解决方案是,在断开连接时,总是在db中将用户设置为脱机-但这意味着,如果用户打开两个选项卡并关闭一个选项卡,它们将“脱机”。然后,我可以为发送的每封邮件将用户重新设置为在线,但这似乎完全是在浪费处理周期,而且在用户真正在线时,仍有大量时间被视为离线

我相信,如果有一种方法可以保证每个客户都能调用OnDisconnected,那么这个问题就会消失。似乎如果我让客户端长时间打开(>10分钟),然后断开连接,
OnDisconnected
永远不会被调用。我将尽我最大的努力确定复制步骤并保持更新

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

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

代码:

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);
                    }
                }
            }
        }
    }
我正在使用分组的方法

发送消息(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);
                    }
                }
            }
        }
    }
公共类连接映射{
专用只读字典\u连接=
新字典();
公共整数计数{
得到{
返回_connections.Count;
}
}
public void Add(T键,字符串connectionId){
锁定(_连接){
哈希集连接;
if(!\u connections.TryGetValue(键,输出连接)){
连接=新的HashSet();
_连接。添加(键,连接);
}
锁(连接){
connections.Add(connectionId);
}
}
}
公共IEnumerable GetConnections(T键){
哈希集连接;
如果(_connections.TryGetValue(键,输出连接)){
返回连接。ToList();
}
返回可枚举的.Empty();
}
公共无效删除(T键,字符串连接ID){
锁定(_连接){
哈希集连接;
if(!\u connections.TryGetValue(键,输出连接)){
返回;
}
锁(连接){
连接。移除(connectionId);
if(connections.Count==0){
_连接。移除(键);
}
}
}
}
}
更新


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

在此处尝试以下示例:


您是否尝试过断开超时、保持活动和热节拍间隔的不同配置@是的,我有。。。这种情况也以小时/天为单位发生-连接在内存中无限期保持活动状态。不要使用数据库进行在线/离线检测。。这应该是服务器的一部分,只需执行
connections.Count
connections.Length
即可获得计数。因为如果你的服务器崩溃,当你刚启动它时,你会有很多在线用户。试着打开传输跟踪。它将显示特定连接ID的详细信息以及在您的案例中引发或未引发的生存期事件;网络可能会消失(实际上一直如此)。使用您的心跳代码来处理断开连接,除非您幸运地得到显式会话结束通知。别指望运气好。我交叉手指希望你能看到。我现在在看-到底是什么部分?我使用内存中的方法来映射用户连接。这似乎使用了一种不同的方法。如果完整的实现细节会有所帮助的话,以下是一切:它使用数据库而不是内存存储来处理重启+扩展。这只是一个扩展的示例,是使其可靠的唯一方法。内存中的方法有一些明显的缺点:-如果服务器重新启动,您将失去所有状态-如果您有多台服务器,则不准确(因为每台服务器对“在线”的用户有不同的看法)如果上述两个问题都不是问题,那么只要您了解何时会发生“重新连接”和“断开连接”,就可以了。OnReconnected可在应用程序池后触发,例如