#c# #asp.net #.net #signalr
#c# #asp.net #.net #signalr
Вопрос:
Версия SignalR: SignalR 2.4.1
Версия .Net Framework: 4.8 (я не использую .Net Core)
Транспорт SignalR: websockets
Я разрабатываю фоновую службу для SignalR (PresenceMonitor), где мне нужно определить, является ли соединение с определенным идентификатором клиента активным или нет. Я использую следующий код для монитора присутствия, чтобы начать с того, чего я хочу достичь:
using System;
using System.Data.Entity.SqlServer;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.AspNet.SignalR.Transports;
namespace UserPresence
{
/// <summary>
/// This class keeps track of connections that the <see cref="UserTrackingHub"/>
/// has seen. It uses a time based system to verify if connections are *actually* still online.
/// Using this class combined with the connection events SignalR raises will ensure
/// that your database will always be in sync with what SignalR is seeing.
/// </summary>
public class PresenceMonitor
{
private readonly ITransportHeartbeat _heartbeat;
private Timer _timer;
// How often we plan to check if the connections in our store are valid
private readonly TimeSpan _presenceCheckInterval = TimeSpan.FromSeconds(10);
// How many periods need pass without an update to consider a connection invalid
private const int periodsBeforeConsideringZombie = 3;
// The number of seconds that have to pass to consider a connection invalid.
private readonly int _zombieThreshold;
public PresenceMonitor(ITransportHeartbeat heartbeat)
{
_heartbeat = heartbeat;
_zombieThreshold = (int)_presenceCheckInterval.TotalSeconds * periodsBeforeConsideringZombie;
}
public void StartMonitoring()
{
if (_timer == null)
{
_timer = new Timer(_ =>
{
try
{
Check();
}
catch (Exception ex)
{
// Don't throw on background threads, it'll kill the entire process
Trace.TraceError(ex.Message);
}
},
null,
TimeSpan.Zero,
_presenceCheckInterval);
}
}
private void Check()
{
using (var db = new UserContext())
{
// Get all connections on this node and update the activity
foreach (var trackedConnection in _heartbeat.GetConnections())
{
if (!trackedConnection.IsAlive)
{
continue;
}
Connection connection = db.Connections.Find(trackedConnection.ConnectionId);
// Update the client's last activity
if (connection != null)
{
connection.LastActivity = DateTimeOffset.UtcNow;
}
else
{
// We have a connection that isn't tracked in our DB!
// This should *NEVER* happen
// Debugger.Launch();
}
}
// Now check all db connections to see if there's any zombies
// Remove all connections that haven't been updated based on our threshold
var zombies = db.Connections.Where(c =>
SqlFunctions.DateDiff("ss", c.LastActivity, DateTimeOffset.UtcNow) >= _zombieThreshold);
// We're doing ToList() since there's no MARS support on azure
foreach (var connection in zombies.ToList())
{
db.Connections.Remove(connection);
}
db.SaveChanges();
}
}
}
}
Проблема, с которой я сталкиваюсь, находится здесь:
// Get all connections on this node and update the activity
foreach (var trackedConnection in _heartbeat.GetConnections())
{
Сканирование всех соединений при большом количестве соединений сильно влияет на производительность моего приложения и приводит к большим скачкам процессора.
В моей базе данных у меня уже есть сопоставление идентификаторов соединений для каждого пользователя. Исходя из этого, у меня уже есть поле в моем кэше для каждого пользователя, независимо от того, имеет ли этот пользователь какое-либо соединение в БД или нет. Эти сопоставления уже находятся в кэше. Я бы просканировал каждое из этих сопоставлений и проверил, является ли соединение (идентификатор соединения) для этого конкретного пользователя живым или нет. Я попытался найти интерфейс ITransportHeartbeat для того же, но, к сожалению, этот интерфейс дает нам только эти четыре метода:
//
// Summary:
// Manages tracking the state of connections.
public interface ITransportHeartbeat
{
//
// Summary:
// Adds a new connection to the list of tracked connections.
//
// Parameters:
// connection:
// The connection to be added.
//
// Returns:
// The connection it replaced, if any.
ITrackingConnection AddOrUpdateConnection(ITrackingConnection connection);
//
// Summary:
// Gets a list of connections being tracked.
//
// Returns:
// A list of connections.
IList<ITrackingConnection> GetConnections();
//
// Summary:
// Marks an existing connection as active.
//
// Parameters:
// connection:
// The connection to mark.
void MarkConnection(ITrackingConnection connection);
//
// Summary:
// Removes a connection from the list of tracked connections.
//
// Parameters:
// connection:
// The connection to remove.
void RemoveConnection(ITrackingConnection connection);
}
Нет метода, с помощью которого я мог бы получить состояние соединения по идентификатору соединения. Есть ли какой-либо способ, с помощью которого я могу получить конкретную информацию о соединении, не сканируя все соединения. Я знаю о традиционном способе получения того, что могло бы использовать это: _heartbeat.getConnections().Выберите(b => b.connectionId). Но этот код также будет сканировать все соединения.
Я также знаю о событии OnDisconnected, которое мы могли бы использовать на самом хабе, но OnDisconnected даже не гарантирует, что оно всегда срабатывает (браузер может закрыться, интернет отключится, перезапуск сайта).
Есть ли какой-либо код, который я мог бы подключить к самому моему хабу, чтобы определить пинг, выполняемый API Heartbeat? Я мог бы сохранить последние пинги для каждого соединения (своего рода денормализовать способ определения последнего пинга) и может определить, является ли это соединение мертвым или нет?
SignalR для .Net Core имеет что-то вроде этого:
var heartbeat = Context.Features.Get<IConnectionHeartbeatFeature>();
heartbeat.OnHeartBeat(MyAction,
но я ищу аналогичную функцию, подобную в SignalR для .NET Framework.
Комментарии:
1. У вас есть несколько вопросов, что затрудняет дать краткий ответ. Как бы вы ожидали выбрать конкретное соединение, не просматривая их все, чтобы определить, какое из них соответствует критериям выбора? И нет, в более старом SignalR нет способа прослушивать сердцебиения, по крайней мере, насколько я знаю.
2. Моя основная цель — проверить, является ли данный connectionid активным или нет. Я не хочу сканировать все соединения.
3. Насколько я знаю — любое соединение, которое фреймворк обнаружит как мертвое, будет автоматически удалено из концентратора. Это, конечно, может занять время, и если вы хотите, чтобы это было быстрее, вам, возможно, придется внедрить свою собственную систему, которая, конечно, должна будет просматривать каждое соединение по отдельности (асинхронное или нет) — или проверить, можно ли изменить какие-либо настройки, чтобы уменьшить таймер сердцебиения.
4. Я хочу использовать этот API, чтобы удалить сопоставления из моей базы данных для мертвых клиентов.
5. Тогда
OnDisconnected
это ваш единственный выбор, и он всегда должен срабатывать независимо от причины отключения клиента.