c# - 使用 SignalR 检测死连接
问题描述
SignalR 版本:SignalR 2.4.1
.Net Framework 版本:4.8(我没有使用 .Net Core)
SignalR 传输:websockets
我正在为 SignalR (PresenceMonitor) 开发后台服务,我需要检测与特定客户端 ID 的连接是否处于活动状态。我正在为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 映射。基于此,我在每个用户的缓存中都有一个字段,无论该用户在数据库中是否有任何连接。这些映射已被缓存。我将扫描每个映射,并检查该特定用户的连接(连接 ID)是否处于活动状态。我尝试寻找相同的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);
}
没有任何方法可以通过 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 的类似功能。
解决方案
推荐阅读
- r - 如何使用 Rstudio 清理我的数据库?
- sharp - 使用 memfs 错误测试尖锐:[错误:输入文件丢失]
- python-3.x - 在烧瓶 api 中找不到 Gcloud
- sql - 计算日期之间可用的周期数并将行值排列为列值
- c# - 这个错误的原因是什么?(.Net 5,C#)
- amazon-web-services - 如何管理 terraform 多个文件并希望以不同的(手动)顺序运行
- python - 如果不是“onefile”案例,则带有 Pyinstaller 的 PyQt5 Gui 可以很好地工作……除此之外,还需要一些更改
- python - Python 3.8 lzma解压大文件增量输入输出
- flutter - Flutter Firebase 匿名认证一打开就登录
- postgresql - FATAL:数据库系统正在关闭 主机密钥验证失败。失去了连接