了解和处理 SignalR 1.x 中的连接生存期事件

作者 :Patrick FletcherTom Dykstra

警告

本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR

本文概述了可以处理的 SignalR 连接、重新连接和断开连接事件,以及可配置的超时和保留设置。

本文假设你已对 SignalR 和连接生存期事件有一定的了解。 有关 SignalR 的简介,请参阅 SignalR - 概述 - 入门。 有关连接生存期事件的列表,请参阅以下资源:

概述

本文包含以下各节:

指向 API 参考主题的链接指向 API 的 .NET 4.5 版本。 如果使用 .NET 4,请参阅 API 的 .NET 4 版本主题

连接生存期术语和方案

OnReconnected SignalR 中心中的事件处理程序可以直接在给定客户端之后OnConnected执行,但不能在之后OnDisconnected执行。 可以在没有断开连接的情况下重新连接的原因是,在 SignalR 中使用“连接”一词的方法有多种。

SignalR 连接、传输连接和物理连接

本文将区分 SignalR 连接传输连接物理连接

  • SignalR 连接 是指客户端与服务器 URL 之间的逻辑关系,由 SignalR API 维护并由连接 ID 唯一标识。 有关此关系的数据由 SignalR 维护,用于建立传输连接。 当客户端调用 Stop 方法或达到超时限制时,当 SignalR 尝试重新建立丢失的传输连接时,关系结束,SignalR 将释放数据。
  • 传输连接 是指客户端与服务器之间的逻辑关系,由以下四个传输 API 之一维护:WebSocket、服务器发送事件、永久帧或长轮询。 SignalR 使用传输 API 创建传输连接,传输 API 依赖于是否存在物理网络连接来创建传输连接。 当 SignalR 终止传输连接或传输 API 检测到物理连接断开时,传输连接结束。
  • 物理连接 是指物理网络链路-线路、无线信号、路由器等。-- 促进客户端计算机和服务器计算机之间的通信。 必须存在物理连接才能建立传输连接,并且必须建立传输连接才能建立 SignalR 连接。 但是,断开物理连接并不总是立即结束传输连接或 SignalR 连接,如本主题后面部分所述。

在下图中,SignalR 连接由中心 API 和 PersistentConnection API SignalR 层表示,传输连接由传输层表示,物理连接由服务器和客户端之间的线条表示。

SignalR 体系结构示意图

在 SignalR 客户端中调用 Start 方法时,会向 SignalR 客户端代码提供与服务器建立物理连接所需的所有信息。 SignalR 客户端代码使用此信息发出 HTTP 请求,并建立使用四种传输方法之一的物理连接。 如果传输连接失败或服务器失败,SignalR 连接不会立即消失,因为客户端仍具有自动重新建立与同一 SignalR URL 的新传输连接所需的信息。 在此方案中,不涉及用户应用程序的干预,当 SignalR 客户端代码建立新的传输连接时,它不会启动新的 SignalR 连接。 SignalR 连接的连续性反映在以下事实中:调用 Start 方法时创建的连接 ID 不会更改。

OnReconnected当传输连接在丢失后自动重新建立时,中心上的事件处理程序将执行。 事件处理程序 OnDisconnected 在 SignalR 连接的末尾执行。 SignalR 连接可能以以下任一方式结束:

  • 如果客户端调用 Stop 方法,则会向服务器发送停止消息,并且客户端和服务器都会立即终止 SignalR 连接。
  • 客户端和服务器之间的连接断开后,客户端会尝试重新连接,服务器将等待客户端重新连接。 如果尝试重新连接失败,并且断开连接超时期限结束,则客户端和服务器将终止 SignalR 连接。 客户端停止尝试重新连接,服务器释放其 SignalR 连接的表示形式。
  • 如果客户端停止运行而没有机会调用 Stop 方法,服务器将等待客户端重新连接,然后在断开连接超时期限后结束 SignalR 连接。
  • 如果服务器停止运行,客户端会尝试重新连接 (重新创建传输连接) ,然后在断开连接超时期限后结束 SignalR 连接。

如果没有连接问题,并且用户应用程序通过调用 Stop 方法结束 SignalR 连接,SignalR 连接和传输连接大约在同一时间开始和结束。 以下部分更详细地描述了其他方案。

传输断开连接方案

物理连接速度可能较慢,或者连接可能会中断。 根据中断的长度等因素,传输连接可能会断开。 然后,SignalR 会尝试重新建立传输连接。 有时传输连接 API 检测到中断并断开传输连接,SignalR 会立即发现连接丢失。 在其他方案中,传输连接 API 和 SignalR 都不会立即意识到连接已丢失。 对于除长轮询之外的所有传输,SignalR 客户端使用名为 keepalive 的函数来检查传输 API 无法检测到的连接丢失。 有关长轮询连接的信息,请参阅本主题后面的 超时和保留设置

当连接处于非活动状态时,服务器会定期向客户端发送 keepalive 数据包。 自本文撰写之日起,默认频率为每 10 秒一次。 通过侦听这些数据包,客户端可以判断是否存在连接问题。 如果未按预期收到 keepalive 数据包,则客户端会在短时间后假定存在连接问题,例如速度缓慢或中断。 如果较长时间后仍未收到 keepalive,则客户端会假定连接已删除,并开始尝试重新连接。

下图演示了当传输 API 无法立即识别的物理连接出现问题时,典型方案中引发的客户端和服务器事件。 此图适用于以下情况:

  • 传输是 WebSocket、永久帧或服务器发送的事件。
  • 物理网络连接中存在不同时期的中断。
  • 传输 API 不会意识到中断,因此 SignalR 依赖于 keepalive 功能来检测它们。

传输断开连接

如果客户端进入重新连接模式,但无法在断开连接超时限制内建立传输连接,则服务器将终止 SignalR 连接。 发生这种情况时,服务器将执行中心的 OnDisconnected 方法,并将断开连接消息排入队列,以发送到客户端,以防客户端稍后设法连接。 如果客户端随后重新连接,它将接收 disconnect 命令并调用 Stop 方法。 在此方案中, OnReconnected 在客户端重新连接时不执行, OnDisconnected 在客户端调用 Stop时不执行。 下图演示了此方案。

传输中断 - 服务器超时

客户端上可能引发的 SignalR 连接生存期事件如下:

  • ConnectionSlow 客户端事件。

    当自收到最后一条消息或 keepalive ping 以来,已超过 keepalive 超时期限的预设比例时引发。 默认的 keepalive 超时警告期是 keepalive 超时的 2/3。 keepalive 超时为 20 秒,因此警告发生在大约 13 秒。

    默认情况下,服务器每 10 秒发送一次 keepalive ping,客户端大约每 2 秒检查一次 keepalive ping, (keepalive 超时值与 keepalive 超时警告值) 差的三分之一。

    如果传输 API 发现断开连接,可能会在保持超时警告期限过前通知 SignalR 断开连接。 在这种情况下, ConnectionSlow 不会引发事件,SignalR 将直接转到事件 Reconnecting

  • Reconnecting 客户端事件。

    当 () 传输 API 检测到连接丢失,或者 (b) 自收到最后一条消息或 keepalive ping 以来的 keepalive 超时期限已过时引发。 SignalR 客户端代码开始尝试重新连接。 如果希望应用程序在传输连接丢失时执行某些操作,则可以处理此事件。 默认的 keepalive 超时期限当前为 20 秒。

    如果客户端代码尝试在 SignalR 处于重新连接模式时调用 Hub 方法,SignalR 将尝试发送命令。 大多数情况下,此类尝试会失败,但在某些情况下,它们可能会成功。 对于服务器发送的事件、永久帧和长轮询传输,SignalR 使用两个通信通道,一个是客户端用来发送消息,另一个用于接收消息。 用于接收的通道是永久打开的通道,这是物理连接中断时关闭的通道。 用于发送的通道仍然可用,因此,如果物理连接已还原,在重新建立接收通道之前,从客户端到服务器的方法调用可能会成功。 在 SignalR 重新打开用于接收的通道之前,不会收到返回值。

  • Reconnected client 事件。

    重新建立传输连接时引发。 执行 OnReconnected 中心中的事件处理程序。

  • Closed javaScript) disconnected 中的 client 事件 (事件。

    当 SignalR 客户端代码在丢失传输连接后尝试重新连接时断开连接超时期限到期时引发。 默认断开连接超时为 30 秒。 (连接结束时也会引发此事件, Stop 因为调用 方法。)

传输 API 未检测到且不延迟从服务器接收 keepalive ping 超过 keepalive 超时警告期的传输连接中断可能不会引发任何连接生存期事件。

某些网络环境会故意关闭空闲连接,而保留数据包的另一个功能是通过让这些网络知道 SignalR 连接正在使用来帮助防止这种情况。 在极端情况下,默认的 keepalive ping 频率可能不足以阻止连接关闭。 在这种情况下,可以将 keepalive ping 配置为更频繁地发送。 有关详细信息,请参阅本主题后面的 超时和保留设置

注意

重要

无法保证此处所述的事件序列。 根据此方案,SignalR 会尝试以可预测的方式引发连接生存期事件,但网络事件有很多变化,底层通信框架(如传输 API)处理这些事件的方式很多。 例如, Reconnected 在客户端重新连接时可能不会引发事件,或者 OnConnected 当尝试建立连接失败时,服务器上的处理程序可能会运行。 本主题仅介绍某些典型情况通常会产生的影响。

客户端断开连接方案

在浏览器客户端中,维护 SignalR 连接的 SignalR 客户端代码在网页的 JavaScript 上下文中运行。 这就是为什么在从一个页面导航到另一个页面时,SignalR 连接必须结束的原因,因此,如果你从多个浏览器窗口或选项卡进行连接,则有多个连接 ID。 当用户关闭浏览器窗口或选项卡,或者导航到新页面或刷新页面时,SignalR 连接会立即结束,因为 SignalR 客户端代码会为你处理浏览器事件并调用 Stop 方法。 在这些情况下,或者应用程序调用 Stop 方法时,在任何客户端平台中,OnDisconnected事件处理程序立即在服务器上执行,并且客户端在 JavaScript) 中命名disconnected事件 (引发Closed事件。

例如,如果用户关闭笔记本电脑 () 时,如果客户端应用程序或运行它的计算机崩溃或进入睡眠状态,则服务器不会通知所发生的情况。 据服务器所知,客户端丢失可能是由于连接中断,客户端可能正在尝试重新连接。 因此,在这些情况下,服务器等待客户端重新连接的机会,并且 OnDisconnected 直到断开连接超时期限到期 (默认) 大约 30 秒才会执行。 下图演示了此方案。

客户端计算机故障

服务器断开连接方案

当服务器脱机时 ,它会重新启动、失败、应用域回收等。-- 结果可能类似于连接丢失,或者传输 API 和 SignalR 可能立即知道服务器已消失,并且 SignalR 可能开始尝试重新连接而不引发 ConnectionSlow 事件。 如果客户端进入重新连接模式,并且服务器恢复或重启或新服务器在断开连接超时期限到期之前联机,则客户端将重新连接到还原的服务器或新服务器。 在这种情况下,SignalR 连接在客户端上继续,并 Reconnected 引发 事件。 在第一台服务器上, OnDisconnected 永远不会执行,在新服务器上执行, OnReconnected 尽管 OnConnected 以前从未在该服务器上为该客户端执行过。 (如果客户端在重新启动或应用域回收后重新连接到同一服务器,则效果相同,因为当服务器重新启动时,它没有先前连接活动的内存。) 下图假定传输 API 立即意识到丢失的连接,因此 ConnectionSlow 不会引发该事件。

服务器故障和重新连接

如果服务器在断开连接超时期限内不可用,则 SignalR 连接将结束。 在此方案中, Closed JavaScript 客户端中 disconnected (事件) 在客户端上引发,但 OnDisconnected 永远不会在服务器上调用。 下图假定传输 API 无法识别丢失的连接,因此 SignalR keepalive 功能检测到该连接并 ConnectionSlow 引发事件。

服务器故障和超时

超时和保留设置

默认 ConnectionTimeoutDisconnectTimeoutKeepAlive 值适用于大多数方案,但如果环境有特殊需求,可以更改。 例如,如果网络环境关闭空闲连接 5 秒,则可能必须降低 keepalive 值。

ConnectionTimeout

此设置表示在关闭传输连接并打开新连接之前保持传输连接打开状态并等待响应的时间量。 默认值为 110 秒。

此设置仅在禁用 keepalive 功能时适用,这通常仅适用于长轮询传输。 下图说明了此设置对长轮询传输连接的影响。

长轮询传输连接

DisconnectTimeout

此设置表示在传输连接丢失后,在引发 Disconnected 事件之前等待的时间量。 默认值为 30 秒。 设置 DisconnectTimeout时, KeepAlive 将自动设置为 值的 1/3 DisconnectTimeout

KeepAlive

此设置表示在通过空闲连接发送保持数据包之前要等待的时间量。 默认值为 10 秒。 此值不能超过值的 1/3 DisconnectTimeout

如果要同时设置 DisconnectTimeoutKeepAlive,请在 之后DisconnectTimeout设置 KeepAlive 。 否则KeepAlive,当自动KeepAlive设置为超时值的 1/3 时DisconnectTimeout,设置将被覆盖。

如果要禁用 keepalive 功能,请将 设置为 KeepAlive null。 对于长时间轮询传输,将自动禁用 Keepalive 功能。

如何更改超时和保留设置

若要更改这些设置的默认值,请在 Global.asax 文件中将其设置为 Application_Start ,如以下示例所示。 示例代码中显示的值与默认值相同。

protected void Application_Start(object sender, EventArgs e)
{
    // Make long polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
    
    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
    
    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
}

如何通知用户断开连接

在某些应用程序中,你可能希望在存在连接问题时向用户显示一条消息。 对于如何以及何时执行此操作,有多个选项。 以下代码示例适用于使用生成的代理的 JavaScript 客户端。

  • 处理 事件以 connectionSlow 在 SignalR 知道连接问题后立即显示消息,然后再进入重新连接模式。

    $.connection.hub.connectionSlow(function() {
        notifyUserOfConnectionProblem(); // Your function to notify user.
    });
    
  • 处理 事件以 reconnecting 在 SignalR 意识到断开连接并进入重新连接模式时显示消息。

    $.connection.hub.reconnecting(function() {
        notifyUserOfTryingToReconnect(); // Your function to notify user.
    });
    
  • 处理 事件以 disconnected 在尝试重新连接超时时显示消息。在这种情况下,重新与服务器建立连接的唯一方法是通过调用 Start 方法重新启动 SignalR 连接,该方法将创建新的连接 ID。 下面的代码示例使用 标志来确保仅在重新连接超时后发出通知,而不是在调用 Stop 方法导致的 SignalR 连接正常结束之后发出通知。

    var tryingToReconnect = false;
    
    $.connection.hub.reconnecting(function() {
        tryingToReconnect = true;
    });
    
    $.connection.hub.reconnected(function() {
        tryingToReconnect = false;
    });
    
    $.connection.hub.disconnected(function() {
        if(tryingToReconnect) {
            notifyUserOfDisconnect(); // Your function to notify user.
        }
    });
    

如何持续重新连接

在某些应用程序中,你可能希望在连接丢失且重新连接尝试超时后自动重新建立连接。为此,可以从 JavaScript 客户端上的事件处理程序 (disconnected事件处理程序) 调用 Start 方法Closed。 在调用 Start 之前,可能需要等待一段时间,以避免在服务器或物理连接不可用时过于频繁地执行此操作。 以下代码示例适用于使用生成的代理的 JavaScript 客户端。

$.connection.hub.disconnected(function() {
   setTimeout(function() {
       $.connection.hub.start();
   }, 5000); // Restart connection after 5 seconds.
});

移动客户端中需要注意的一个潜在问题是,当服务器或物理连接不可用时,连续重新连接尝试可能会导致不必要的电池耗尽。

如何在服务器代码中断开客户端的连接

SignalR 版本 1.1.1 没有用于断开客户端连接的内置服务器 API。 将来有 添加此功能的计划。 在当前的 SignalR 版本中,断开客户端与服务器的连接的最简单方法是在客户端上实现断开连接方法,并从服务器调用该方法。 以下代码示例演示使用生成的代理的 JavaScript 客户端的断开连接方法。

var myHubProxy = $.connection.myHub
myHubProxy.client.stopClient = function() {
    $.connection.hub.stop();
};

警告

安全性 - 这种用于断开客户端连接的方法和建议的内置 API 都不会解决运行恶意代码的被黑客攻击客户端的情况,因为客户端可以重新连接,或者被黑客攻击的代码可能会删除 stopClient 该方法或更改它的作用。 实现有状态拒绝服务 (DOS) 保护的适当位置不在框架或服务器层中,而是在前端基础结构中。