SignalR 版本:SignalR 2.4.1
.Net Framework 版本:4.8(我没有使用 .Net Core)
SignalR 传输:websockets
我正在为 SignalR (PresenceMonitor) 开发后台服务,我需要在其中检测具有特定 clientid 的连接是否处于活动状态。 我正在使用 Presence Monitor 的以下代码来开始我想要实现的目标:
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())
{
在有大量连接时扫描所有连接会严重影响我的应用程序的性能并导致大量 CPU 峰值。
在我的数据库中,我已经有了每个用户的连接 ID 的映射。基于这一点,无论该用户是否在 db 中有任何连接,我的缓存中已经有每个用户的字段。这些映射已经被缓存。我会扫描这些映射中的每一个,并检查该特定用户的连接(连接 ID)是否处于活动状态。我尝试寻找 ITransportHeartbeat Interface 相同但不幸的是,该接口仅提供了以下四种方法:
//
// 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);
}
没有任何方法可以通过 connectionid 获取连接状态。有什么方法可以让我在不扫描所有连接的情况下获得特定的连接信息。我知道传统的获取方式可以使用这个:_heartbeat.GetConnections().Select(b => b.ConnectionId)。但该代码也会扫描所有连接。
我也知道我们可以在集线器本身上使用 OnDisconnected 事件,但 OnDisconnected 甚至不能保证始终触发(浏览器可以关闭、互联网关闭、站点重新启动)。
是否有任何代码可以在我的集线器中挂钩以检测 Heartbeat API 执行的 ping 操作?我可以存储每个连接的最后一个 ping(有点非规范化检测最后一个 ping 的方式)并且可以检测该连接是否已死?
SignalR for .Net Core 有类似的东西:
var heartbeat = Context.Features.Get<IConnectionHeartbeatFeature>();
heartbeat.OnHeartBeat(MyAction,
但我正在寻找类似 SignalR for .NET Framework 的功能。