SignalR OnDisconnected - uma maneira confiável de lidar com “Usuário está online” para salas de chat?

StackOverflow https://stackoverflow.com//questions/21066657

Pergunta

Estou implementando uma sala de chat.Até agora, tudo bem - os usuários podem enviar mensagens de seus navegadores por meio de um cliente JS, e eu posso usar um cliente C# para fazer a mesma coisa - essas mensagens são transmitidas para outros usuários.Agora estou tentando implementar "usuários online".

Minha abordagem é a seguinte:

  • OnConnected - atualize o usuário no banco de dados para ser IsOnline = true
  • OnDisconnected - se o usuário não tiver outras conexões, atualize o usuário no banco de dados para ser IsOnline = false
  • Estou armazenando o estado no banco de dados porque preciso consultar o banco de dados em busca de miniaturas de usuários - isso parecia uma alternativa simples para trabalhar com os dicionários no hub.

O problema que estou encontrando é que OnDisconnected nem sempre é chamado para cada ID de cliente - as conexões obsoletas estão impedindo que o bit "se o usuário não tiver nenhuma outra conexão" seja resolvido como verdadeiro, para que o usuário esteja sempre "online".

Uma solução hacky que consigo pensar é sempre definir o usuário para offline no banco de dados após OnDisconnect - mas isso significa que se o usuário abrir duas abas e fechar uma, elas ficarão “offline”.Eu poderia então redefinir o usuário para on-line para cada mensagem enviada, mas isso parece um desperdício total de ciclos de processamento e ainda deixa um tempo em que o usuário é visto como off-line, quando está realmente on-line.

Acredito que se houvesse uma maneira de garantir que o OnDisconnected fosse chamado para todos os clientes, esse problema desapareceria.Isto parece como se eu deixasse os clientes abertos por muito tempo (> 10 minutos) e depois desconectasse, OnDisconnected nunca é chamado.Tentarei o meu melhor para identificar as etapas de reprodução e mantê-las atualizadas.

Então - esta é uma abordagem válida para lidar com o status online?Se sim, o que mais pode ser feito para garantir que OnDisconnected está disparando para todas as conexões, eventualmente?

Esse problema me preocupa porque as conexões existentes continuarão a crescer com o tempo, se não me engano, eventualmente transbordando devido a conexões de estado não tratadas.

Código:

estou usando o Em memória abordagem aos agrupamentos.

Envio de mensagens (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());
            }
        }

Gestão estadual:

    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
        }
    }

Mapeamento de conexão:

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);
                    }
                }
            }
        }
    }

Atualizar

De acordo com a sugestão de dfowler, uma abordagem alternativa seria implementar o mapeamento no banco de dados em vez de na memória, dessa forma, mais metadados podem ser usados ​​para identificar conexões zumbificadas.No entanto, espero uma solução para o problema da memória, em vez de re-arquitetar longe de um abordagem recomendada isso já está implementado.

Foi útil?
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top