c# signalr signalr-hub signalr.client

c# - SignalR OnDisconnected: ¿una forma confiable de manejar "El usuario está en línea" para la sala de chat?



signalr-hub signalr.client (1)

Estoy implementando una sala de chat. Hasta ahora, todo bien: los usuarios pueden enviar mensajes desde sus navegadores a través de un cliente JS, y puedo usar un cliente C # para hacer lo mismo: estos mensajes se transmiten a otros usuarios. Ahora, estoy tratando de implementar "usuarios en línea".

Mi enfoque es el siguiente:

  • OnConnected : actualiza el usuario en la base de datos para que sea IsOnline = true
  • OnDisconnected : si el usuario no tiene otras conexiones, actualice al usuario en la base de datos para que sea IsOnline = false
  • Estoy almacenando el estado en la base de datos porque tengo que consultar en la base de datos las miniaturas de los usuarios de todos modos, esto parecía una alternativa simple a trabajar con los diccionarios en el concentrador.

El problema que me encuentro es que OnDisconnected no siempre recibe llamadas para cada ID de cliente: las conexiones obsoletas impiden que el bit "si el usuario no tiene otras conexiones" se resuelva en verdadero, por lo que el usuario siempre está " en línea".

Una solución intrépida en la que puedo pensar es configurar siempre el usuario como fuera de línea en la base de OnDisconnect en OnDisconnect , pero esto significa que si el usuario abre dos pestañas y cierra una, estará "fuera de línea". Luego podría volver a configurar el usuario en línea para cada mensaje que se envíe, pero esto parece ser una pérdida total de ciclos de procesamiento y aún deja una porción de tiempo donde el usuario se ve como fuera de línea, cuando realmente están en línea.

Creo que si hubiera una manera de garantizar que OnDisconnected sea llamado para cada cliente, este problema desaparecerá. Parece que si dejo a los clientes abiertos por un tiempo prolongado (> 10 minutos) y luego me desconecto, OnDisconnected nunca recibe llamadas. Haré todo lo posible para identificar los pasos de reproducción y mantener esto actualizado.

Entonces, ¿es este un enfoque válido para manejar el estado en línea? Si es así, ¿qué más se puede hacer para asegurar que OnDisconnected se dispare para cada conexión, eventualmente?

Este problema me preocupa porque las conexiones existentes solo seguirán creciendo con el tiempo, si no me equivoco, eventualmente se desbordarán debido a las conexiones de estado no manejadas.

Código:

Estoy usando el enfoque en In-memory para agrupaciones.

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

Administración del Estado:

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

Actualizar

Según la sugerencia de dfowler, un enfoque alternativo sería implementar el mapeo in-db en lugar de in-memory, de esta manera se pueden usar más metadatos para identificar conexiones zombificadas. Sin embargo, espero una solución para el problema en la memoria, en lugar de volver a diseñar un In-memory que no esté implementado.