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

警告

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

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

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

本主题中使用的软件版本

本主题的早期版本

有关 SignalR 早期版本的信息,请参阅 SignalR 旧版本

问题和评论

请留下反馈,说明你如何喜欢本教程,以及我们可以在页面底部的评论中改进的内容。 如果你有与本教程不直接相关的问题,可以将其发布到 ASP.NET SignalR 论坛StackOverflow.com

概述

本文包含以下各节:

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

连接生存期术语和方案

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 无法检测到的连接丢失。 有关长轮询连接的信息,请参阅本主题后面的 超时和保留设置

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

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

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

传输断开连接

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

传输中断 - 服务器超时

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

  • ConnectionSlow client 事件。

    当自收到最后一条消息或 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 客户端事件。

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

  • Closed javaScript) disconnected 中的客户端事件 (事件。

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

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

某些网络环境会故意关闭空闲连接,而 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 引发 事件。

服务器故障和超时

超时和 keepalive 设置

默认 ConnectionTimeout的 、 DisconnectTimeoutKeepAlive 值适用于大多数方案,但如果环境有特殊需求,则可以更改。 例如,如果网络环境关闭空闲连接 5 秒,则可能需要减小 keepalive 值。

ConnectionTimeout

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

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

长轮询传输连接

DisconnectTimeout

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

KeepAlive

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

如果要同时DisconnectTimeout设置 和 KeepAlive,请在 之后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);
    
    RouteTable.Routes.MapHubs();
}

如何通知用户断开连接

在某些应用程序中,你可能希望在出现连接问题时向用户显示一条消息。 对于如何以及何时执行此操作,有多个选项。 以下代码示例适用于使用生成的代理的 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 版本 2 没有用于断开客户端连接的内置服务器 API。 将来有 计划添加此功能。 在当前的 SignalR 版本中,断开客户端与服务器的连接的最简单方法是在客户端上实现断开连接方法,并从服务器调用该方法。 以下代码示例演示使用生成的代理的 JavaScript 客户端的断开连接方法。

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

警告

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

检测断开连接的原因

SignalR 2.1 向服务器 OnDisconnect 事件添加重载,指示客户端是否故意断开连接而不是超时。 StopCalled 如果客户端显式关闭连接,则 参数为 true。 在 JavaScript 中,如果服务器错误导致客户端断开连接,错误信息将作为 $.connection.hub.lastError传递到客户端。

C# 服务器代码: stopCalled 参数

public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
    if (stopCalled)
    {
        Console.WriteLine(String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId));
    }
    else
    {
        Console.WriteLine(String.Format("Client {0} timed out .", Context.ConnectionId));
    }
            
    return base.OnDisconnected(stopCalled);
}

JavaScript 客户端代码:在 事件中disconnect访问lastError

$.connection.hub.disconnected(function () {
    if ($.connection.hub.lastError) 
        { alert("Disconnected. Reason: " +  $.connection.hub.lastError.message); }
});