SignalR OnDisconnected - una manera confiable para manejar "el Usuario está en Línea" para la sala de chat?

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

Pregunta

Estoy implementando una sala de chat.Tan lejos, tan bueno - los usuarios pueden enviar mensajes desde sus navegadores a través de un JS cliente, y puedo usar un cliente de C# para hacer la misma cosa - estos mensajes de difusión a otros usuarios.Ahora, estoy tratando de implementar "usuarios en línea".

Mi planteamiento es el siguiente:

  • OnConnected - actualizar el Usuario en la base de datos para ser IsOnline = true
  • OnDisconnected - si el Usuario no tiene ningún otro tipo de conexiones, actualizar el usuario en la base de datos a ser IsOnline = false
  • Estoy en el estado de almacenamiento en la base de datos porque tengo una consulta a la db para que el usuario las miniaturas de todos modos - esto parecía una alternativa sencilla para trabajar con los Diccionarios en el centro.

El problema que estoy encontrando es que OnDisconnected no siempre se llama para cada Id de cliente - la añeja conexiones son la prevención de la "si el usuario no tiene ninguna otra conexión" bits de resolución a verdadero, por lo que el usuario está siempre "en línea".

Una hacky solución que se me ocurre es siempre definir el usuario fuera de línea en la base de datos sobre OnDisconnect - pero esto significa que si el usuario abre dos pestañas y se cierra uno, que será "sin conexión".Entonces yo podría re-definir el usuario en línea para cada mensaje que se envía, pero esto parece ser una pérdida total de ciclos de procesamiento y todavía deja un pedazo de tiempo en el que el usuario es visto como fuera de línea, cuando en realidad se encuentran en línea.

Yo creo que si que hay una manera de garantizar que OnDisconnected se llama para cada cliente, este problema desaparece.Es parece como si me dejan los clientes abierta durante un largo tiempo (> 10 minutos) y, a continuación, desconectar, OnDisconnected nunca se llama.Voy a intentar mi mejor esfuerzo para identificar los pasos de reproducción y mantener esto actualizado.

Así Es este un enfoque válido para la manipulación de estado en línea?Si es así, ¿qué más se puede hacer para asegurarse de que OnDisconnected es de cocción para cada conexión, con el tiempo?

Este problema me preocupa porque las Conexiones existentes solo seguirá creciendo con el tiempo, si no me equivoco, finalmente desbordante debido a la no controlada estado de las conexiones.

Código:

Estoy usando el En memoria enfoque de las agrupaciones.

El Envío De Mensajes (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());
            }
        }

Estado de gestión:

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

Actualización

Por dfowler sugerencia de un enfoque alternativo sería implementar en db de asignación, en lugar de en la memoria, de esta manera más de metadatos pueden ser utilizados para identificar zombified conexiones.Yo estoy esperando una solución para el problema de memoria a pesar de que, en lugar de re-arquitecto lejos de un enfoque recomendado que ya está implementado.

¿Fue útil?

Solución

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top