首页 > 解决方案 > 使用 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 的类似功能。

标签: c#asp.net.netsignalr

解决方案


推荐阅读