SignalR OnDisconnected-надежный способ обработки "пользователь онлайн" для чата?
Я ввожу в чат. До сих пор так хорошо - пользователи могут отправлять сообщения из своих браузеров через клиент JS, и я могу использовать клиент C#, чтобы сделать то же самое - эти сообщения транслируются другим пользователям. Теперь я пытаюсь реализовать "онлайн-пользователей".
мой подход следующий:
-
OnConnected
- обновите пользователя в БД, чтобы быть IsOnline = true -
OnDisconnected
- если у пользователя нет других подключений, обновите пользователя в БД бытьIsOnline = false
- я сохраняю состояние в БД, потому что мне все равно нужно запросить БД для эскизов пользователей - это казалось простой альтернативой работе со словарями в концентраторе.
проблема, с которой я сталкиваюсь, заключается в том, что OnDisconnected
не всегда вызывается для каждого идентификатора клиента-устаревшие соединения предотвращают бит " Если у пользователя нет других соединений "от разрешения до true, поэтому пользователь всегда"онлайн".
одно хакерское решение, которое я могу придумать, - это всегда настройки пользователей в сети дБ при OnDisconnect
- но это означает, что если пользователь открывает две вкладки и закрывается, они будут "не в сети". Затем я мог бы повторно настроить пользователя на онлайн для каждого сообщения, которое отправляется, но это похоже на полную трату циклов обработки и все еще оставляет кусок времени, когда пользователь рассматривается как автономный, когда они действительно онлайн.
Я считаю, что если бы была способ гарантировать, что OnDisconnected вызывается для каждого клиента, эта проблема уйдет. Это кажется например, если я оставляю клиентов открытыми в течение длительного времени (>10 минут), а затем отключаюсь,OnDisconnected
никогда не вызывается. Я постараюсь изо всех сил, чтобы точно определить шаги repro и держать это в курсе.
Итак-это правильный подход к обработке онлайн-статуса? Если да, то что еще можно сделать для обеспечения этого OnDisconnected
стрельбы для каждого соединения, в конце концов?
этот проблема беспокоит меня, потому что существующие соединения будут просто продолжать расти с течением времени, если я не ошибаюсь, в конечном итоге переполняются из-за необработанных соединений состояния.
код:
я использую в памяти подход к группировки.
Отправка Сообщений (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
}
}
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);
}
}
}
}
}
обновление
по предложению dfowler альтернативным подходом было бы реализовать отображение в БД вместо в памяти, таким образом, больше метаданных можно использовать для идентификации зомбированных соединений. Я надеюсь на решение проблемы в памяти, хотя, вместо того, чтобы перепроектировать вдали от рекомендуемый подход это уже реализовано.
1 ответов
попробуйте следовать этому образцу здесь:
https://github.com/DamianEdwards/NDCLondon2013/tree/master/UserPresence