【问题标题】:SignalR: Client and server both "reconnect" but pushes don't reach clientSignalR:客户端和服务器都“重新连接”,但推送未到达客户端
【发布时间】:2015-08-18 07:05:12
【问题描述】:

我在 Azure 上有一个 C# SignalR 客户端 (2.2) 和 ASP.NET MVC SignalR 服务器。当在服务器端创建一个新的“实体”时,它会使用以下内容向客户端推送一个简单的通知:

public static class EntityHubHelper
{
    private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();

    public static void EntityCreated(IdentityUser user, Entity entity)
    {
        _hubContext.Clients.User(user.UserName).EntityCreated(entity);
    }
}

[Authorize]
public class EntityHub : Hub
{
    // Just tracing overrides for OnConnected/OnReconnected/OnDisconnected
}

客户端或服务器偶尔会重新连接,这是意料之中的,但我看到两者都重新连接(例如重新启动 Web 服务器)但随后客户端停止获取数据的情况。

这似乎发生在 1-2 天没有数据被推送之后,然后最终推送被错过。

我们的客户追踪:

15/08/02 03:57:23 DEBUG SignalR: StateChanged: Connected -> Reconnecting
15/08/02 03:57:28 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:31 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:47 DEBUG SignalR: StateChanged: Reconnecting -> Connected
15/08/02 03:57:47 INFO SignalR OnReconnected

我们的服务器追踪:

8/2/2015 3:57:57 AM     [SignalR][OnReconnected] Email=correspondinguser@example.com, ConnectionId=ff4e472b-184c-49d4-a662-8b0e26da43e2

我使用服务器默认值进行 keepalive 和超时(10 秒和 30 秒),它通常使用 websockets(在 Azure 上启用,标准因此没有限制)。

我有两个问题:

(1) 在 websocket 情况下,客户端如何发现服务器已重新启动(在这种情况下,它会丢失对所述客户端存在的记忆)?是否服务器的 10s/30s 设置在初始连接期间被推低,客户端决定服务器在 30s 后消失?

(2) 如何调试这种情况?有什么方法可以证明客户端实际上仍在接收保活,所以我知道我在其他地方遇到了一些灾难性问题?

【问题讨论】:

    标签: .net signalr signalr.client


    【解决方案1】:

    经过各种测试和修复,当从用户映射到连接 ID 时,问题似乎出在 IUserIdProvider 中。使用 SignalR 消息添加客户端发起的 keepalive 表明客户端和服务器确实已重新连接,并且连接保持健康,但从服务器推送到客户端的消息在 1-2 天后进入黑洞,可能与网站发布/应用程序域刷新有关参与。

    我使用@davidfowl in this post 推荐的this user presence sampleIUserIdProvider 替换为SQL Azure (various options explained here),并针对我现有的用户/身份验证方案进行了定制。但是,它需要在 PresenceMonitor.cs 中进行一些额外的更改以提高可靠性:

    • 我不得不将periodsBeforeConsideringZombie 从 3 增加到 6,因为它在 30 秒时删除了“僵尸”连接,而他们直到 50 秒左右才会断开连接。这意味着连接有时会在 30-50 秒范围内的某个地方重新连接,并且不会在数据库中进行跟踪。
    • 我必须修复在数据库中找不到的心跳跟踪连接的处理。

    样本在UserPresence.Check()中有如下代码:

    // 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();
    }
    

    但是,即使periodsBeforeConsideringZombie 在 6 时,显然不应该发生的情况 - 看到在数据库中找不到的心跳跟踪连接 - 也很常见(比如 10% 的新连接)。这是因为集线器的 OnConnected 事件有时可能会有点慢触发,因此如果您的 10 秒计时器处理程序“幸运”,您会在心跳列表中看到一个新连接。

    我在UserPresence 中使用此代码,而不是为连接提供两个计时器滴答声,或根据计时器“运气”在 10 秒到 20 秒之间触发 OnConnected。如果它仍然没有被数据库跟踪,我断开它以便客户端再次连接(处理 OnClosed)并且不是消息的黑洞(因为我为用户循环数据库连接以推送消息)。

    private HashSet<string> notInDbReadyToDisconnect = new HashSet<string>();
    
    private void Check()
    {
        HashSet<string> notInDbReadyToDisconnectNew = new HashSet<string>();
    
        ...
    
            else
            {
                // REMOVED: // We have a connection that isn't tracked in our DB!
                // REMOVED: // This should *NEVER* happen
                // REMOVED: // Debugger.Launch();
                string format;
                if (notInDbReadyToDisconnect.Contains(trackedConnection.ConnectionId))
                {
                    trackedConnection.Disconnect();
                    format = "[SignalR][PresenceMonitor] Disconnecting active connection not tracked in DB (#2), ConnectionId={0}";
                }
                else
                {
                    notInDbReadyToDisconnectNew.Add(trackedConnection.ConnectionId);
                    format = "[SignalR][PresenceMonitor] Found active connection not tracked in DB (#1), ConnectionId={0}";
                }
            }
    
        ...
    
    
        notInDbReadyToDisconnect = notInDbReadyToDisconnectNew;
    
        ...
    }
    

    它为单个服务器完成这项工作,但可能需要将 HashSet 移动到数据库以处理横向扩展。

    在这一切之后,一切都非常可靠,我的服务器推送代码仍然非常简单:

    public static class EntityHubHelper
    {
        private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();
    
        public static void EntityCreated(User user, Entity entity)
        {
            List<string> connectionIds = user.PushConnections.Select(c => c.ConnectionId).ToList();
            _hubContext.Clients.Clients(connectionIds).EntityCreated(entity);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-09
      相关资源
      最近更新 更多