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

警告

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

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

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

本主题中使用的软件版本

本主题的早期版本

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

问题和评论

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

概述

本文包含以下各节:

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

连接生存期术语和方案

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

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

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

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

在下图中,SignalR 连接由 Hubs 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 依赖于保持功能来检测中断。

传输断开连接

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

传输中断 - 服务器超时

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

  • ConnectionSlow 客户端事件。

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

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

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

  • Reconnecting 客户端事件。

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

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

  • Reconnected 客户端事件。

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

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

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

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

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

注意

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

客户端断开连接方案

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

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

客户端计算机故障

服务器断开连接方案

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

服务器故障和重新连接 如果服务器在断开连接超时期限内不可用,SignalR 连接将结束。 在此方案中,客户端上会引发“已关闭”事件(JavaScript 客户端中的“已断开连接”),但在服务器上永远不会调用“OnDisconnected”。 下图假定传输 API 不会意识到丢失的连接,因此 SignalR 保持功能检测到该 API 并引发“ConnectionSlow”事件。

服务器失败和超时

超时和保留设置

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

ConnectionTimeout

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

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

长轮询传输连接

DisconnectTimeout

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

KeepAlive

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

如果要同时设置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。 下面的代码示例使用标志来确保仅在重新连接超时后发出通知,而不是在调用该方法导致正常结束 SignalR 连接之后发出通知 Stop

    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.
        }
    });
    

如何持续重新连接

在某些应用程序中,你可能希望在连接丢失且尝试重新连接超时后自动重新建立连接。为此,可以从事件处理程序(disconnectedJavaScript 客户端上的事件处理程序)调用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 客户端代码:访问 lastError 事件 disconnect

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