ASP.NET Core SignalR JavaScript 客户端

ASP.NET Core SignalR JavaScript 客户端库使开发人员能够调用服务器端 SignalR 中心代码。 本文介绍如何使用 API 连接到 SignalR 中心,并调用 JavaScript 中心和客户端方法。

查看或下载示例代码如何下载

安装 SignalR 客户端包

SignalR JavaScript 客户端库以 npm 包的形式提供。 可通过多种方式安装客户端库:

  • 运行 npm 命令,在 Visual Studio 的 程序包管理器 Console 中。
  • 在 Visual Studio Code 的集成终端中运行 npm 命令。
  • 引用内容分发网络(CDN)上托管的客户端库的副本。
  • 使用 LibMan 并从 CDN 托管的客户端库安装特定的客户端库文件。

通过 npm 安装

npm窗口中运行以下 命令:

npm init -y
npm install @microsoft/signalr

npm 在 node_modules\@microsoft\signalr\dist\browser 文件夹中安装包内容。

完成设置:

  1. 创建 wwwroot/lib/signalr 文件夹。

  2. signalr.js 文件复制到 wwwroot/lib/signalr 文件夹。

现在,您可以在 <script> 元素中引用已安装的 SignalR JavaScript 客户端。 例如:

<script src="~/lib/signalr/signalr.js"></script>

使用 CDN

若要在没有 npm 的情况下使用客户端库,请引用客户端库的 CDN 托管副本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

代码示例指定版本 6.0.1。 若要获取最新的客户端库版本,请选择以下 CDN 之一:

通过 LibMan 安装

另一种方法是使用 LibMan 并仅安装 CDN 托管客户端库中的特定客户端库文件。 例如,只能将缩小的 JavaScript 文件添加到项目中。

有关此方法的详细信息,请参阅 “添加 SignalR 客户端库”。

连接到中心

以下代码创建并启动连接。 中心名称不区分大小写:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

跨源连接 (CORS)

通常,浏览器从与请求页面相同的域加载连接。 但是,有时需要连接到另一个域。

对于 跨域请求,客户端代码 必须使用 绝对 URL 而不是相对 URL。 使用跨域请求时,请更改为 .withUrl("/chathub").withUrl("https://{App domain name}/chathub")

为了防止恶意站点从另一站点读取敏感数据,默认情况下禁用跨域连接。 若要允许跨域请求,请启用 CORS

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

在调用 MapHub 方法之前,必须先调用 UseCors 方法。

从客户端调用中心方法

JavaScript 客户端通过 HubConnectioninvoke 方法在中心调用公共方法。 invoke 方法接受:

  • 中心方法的名称。
  • 中心方法中定义的任何参数。

在以下突出显示的代码中,中心上的方法名称为 SendMessage。 传递到 invoke 的第二个和第三个参数映射到中心方法的 usermessage 参数:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

仅当使用处于 Default 模式的 Azure SignalR Service 时,才支持从客户端调用中心方法。 有关详细信息,请参阅常见问题解答(azure-signalr GitHub 存储库)。

该方法 invoke 返回 JavaScript Promise 对象。 当服务器上的方法返回时,Promise 对象会以返回值(如果有)作为其解析结果。 如果服务器上的方法引发错误,则会 Promise 拒绝该对象并显示错误消息。 若要处理这些情况,请使用 asyncawaitPromise 对象 thencatch 方法。

JavaScript 客户端还可以通过 HubConnectionsend 方法调用中心上的公共方法。 与 invoke 方法不同,send 方法不会等待来自服务器的响应。 该方法 send 返回 JavaScript Promise 对象。 当消息发送到服务器时,Promise 对象会被解析。 如果发送消息时发生错误,则会 Promise 拒绝该对象并显示错误消息。 若要处理这些情况,请使用 asyncawaitPromise 对象 thencatch 方法。

使用 send不会 等待服务器接收到消息,因此无法从服务器返回数据或错误信息。

从中心调用客户端方法

若要从集线器接收消息,请使用 HubConnectionon 方法定义一个方法。 on 方法接受:

  • JavaScript 客户端方法的名称。
  • 中心传递给方法的参数。

在下面的示例中,方法名称为 ReceiveMessage。 参数名称为 usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

当服务器端代码使用 SendAsync 方法调用它时,connection.on 中的上述代码将运行:

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

SignalR 通过匹配在 SendAsyncconnection.on 中定义的方法名称和参数来确定要调用的客户端方法。

一个最佳实践是在on之后调用HubConnection上的start方法。 这种方法可确保在收到任何消息之前,处理程序已完成注册。

错误处理和日志记录

当客户端无法连接或发送消息时,使用 console.error 将错误输出到浏览器控制台:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

在建立连接时,通过传递记录器和要记录的事件类型来设置客户端日志跟踪。 使用指定的日志级别和更高级别记录消息。 可用的日志级别如下所示:

日志级别 记录的数据 Description
signalR.LogLevel.Error 错误消息 仅记录 Error 消息。
signalR.LogLevel.Warning 有关潜在错误的警告消息 记录 WarningError 消息。
signalR.LogLevel.Information 无错误的状态消息 记录 InformationWarningError 消息。
signalR.LogLevel.Trace 跟踪消息 记录所有内容,包括在中心和客户端之间传输的数据。

使用 HubConnectionBuilder 上的 configureLogging 方法配置日志级别。 消息将记录到浏览器控制台:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

重新连接客户端

若要与客户端重新连接,可以设置自动重新连接或手动配置连接。

自动重新连接

JavaScript 客户端SignalR可以配置为使用 HubConnectionBuilder 上的 WithAutomaticReconnect 方法自动重新连接。 默认情况下,它不会自动重新连接。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

在没有任何参数的情况下,WithAutomaticReconnect 将客户端配置为在每次尝试重新连接之前分别等待 0、2、10 和 30 秒。 四次尝试失败后,它停止尝试重新连接。

在开始任何重新连接之前,HubConnection

  • 转换为 HubConnectionState.Reconnecting 状态,并触发其 onreconnecting 回调函数。
  • 不会进入 Disconnected 状态,也不会触发其 onclose 回调,这与未配置自动重新连接的 HubConnection 一样。

重新连接方法提供了警告用户连接丢失以及禁用 UI 元素的机会。

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

如果客户端在其前四次尝试中成功重新连接,则 HubConnection 转换回 Connected 状态并触发其 onreconnected 回调。 此方法提供了通知用户连接现已重新建立的机会。

由于在服务器看来,该连接完全是新的,因此会向onreconnected回调提供一个新的connectionId

如果 HubConnection 被配置为 跳过协商,则 onreconnected 回调的 connectionId 参数为 undefined

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect 不配置 HubConnection 重试初始启动失败,因此需要手动处理启动失败:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

如果客户端在其前四次尝试中未成功重新连接,则 HubConnection 将转换为 Disconnected 状态并触发其 onclose 回调。 此方法提供了一个机会,告知用户连接永久丢失,并尝试刷新页面:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

若要自定义断开连接前的重连尝试次数,或更改重连时间间隔,withAutomaticReconnect 接受一个数字数组,表示每次开始重新连接尝试前需等待的延迟时间(以毫秒为单位)。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

前面的示例将 HubConnection 配置为在连接丢失后立即开始尝试重新连接。 默认配置还会等待零(0)秒尝试重新连接。

  • 如果第一次重新连接尝试失败,第二次重新连接尝试也会立即启动,而不是等待默认配置中定义的 2 秒。

  • 如果第二次重新连接尝试失败,第三次重新连接尝试将在 10 秒内启动,这是默认配置中定义的相同行为。

  • 配置的重连时序设置与默认行为不同,即在第三次重连尝试失败后停止。 在默认配置下,再过 30 秒后会再进行一次重新连接尝试。

若想要更好地控制自动重新连接尝试的时间安排和次数,则 withAutomaticReconnect 需接受实现 IRetryPolicy 接口的对象,该对象具有一个名为 nextRetryDelayInMilliseconds 的方法。 nextRetryDelayInMilliseconds 接受一个 RetryContext 类型的参数。 有 RetryContext 三个属性: previousRetryCount (类型 number)、 elapsedMilliseconds (类型 number)和 retryReason (类型 Error)。

  • 在第一次重新连接尝试之前, previousRetryCount 并且 elapsedMilliseconds 均为零(0),并且 retryReason 是导致连接丢失的错误。

  • 每次重试尝试失败后, previousRetryCount 都会递增一次, elapsedMilliseconds 更新以反映到目前为止重新连接所花费的时间(以毫秒为单位),并且 retryReason 是导致上次重新连接尝试失败的错误。

nextRetryDelayInMilliseconds 必须返回一个数字,表示下次重连尝试前需要等待的毫秒数;如果 HubConnection 应停止重连,则返回 null

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

或者,可以编写代码以手动重新连接客户端,如下一部分所示。

手动重新连接

下面的代码演示典型的手动重新连接方法:

  1. 若要启动连接,请创建一个函数。 在本例中,函数 start

  2. 在连接的 start 事件处理程序中调用 onclose 函数。

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

生产环境中的实现通常使用指数退避,或按指定次数重试。

浏览器睡眠选项卡

某些浏览器具有 选项卡冻结选项卡睡眠 功能,可减少非活动选项卡的计算机资源使用率。 但是,此功能可能会导致 SignalR 连接关闭并导致不良用户体验。

浏览器会采用启发式规则来确定某个标签页是否应进入休眠状态,例如:

  • 播放音频
  • 持有 Web 锁
  • 持有 IndexedDB
  • 正在连接到 USB 设备
  • 采集视频或音频
  • 正在镜像中
  • 捕获窗口或屏幕

启发式方法可能会随时间而变化,也会因浏览器不同而有所差异。 请查看支持矩阵,找出最适合你的方案的方法。

若要避免使应用进入休眠状态,应用应触发浏览器采用的某种启发式判断机制。

下面的代码示例演示如何使用 Web 锁 使选项卡保持唤醒并避免意外的连接关闭:

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

在前面的代码中:

  • Web Lock 是实验性的。 条件检查确认浏览器支持 Web 锁定。
  • JavaScript Promise 解析器(lockResolver)会被存储下来,以便在允许标签页休眠时释放锁。
  • 连接关闭时,通过调用 lockResolver()释放锁。 释放锁定时,选项卡可进入睡眠状态。

作者:Rachel Appel

ASP.NET Core SignalR JavaScript 客户端库使开发人员能够调用服务器端中心代码。

查看或下载示例代码如何下载

安装 SignalR 客户端包

SignalR JavaScript 客户端库以 npm 包的形式提供。 以下部分概述了安装客户端库的不同方法。

通过 npm 安装

对于 Visual Studio,在根文件夹中从“程序包管理器控制台”运行以下命令。 对于 Visual Studio Code,从“集成终端”运行以下命令。

npm init -y
npm install @microsoft/signalr

npm 在 node_modules\@microsoft\signalr\dist\browser 文件夹中安装包内容。 在 wwwroot\lib 文件夹下创建名为 signalr 的新文件夹。 将 signalr.js 文件复制到 wwwroot\lib\signalr 文件夹。

在 SignalR 元素中引用 <script> JavaScript 客户端。 例如:

<script src="~/lib/signalr/signalr.js"></script>

使用内容分发网络 (CDN)

若要在没有 npm 的情况下使用客户端库,请引用客户端库的 CDN 托管副本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

可在以下 CDN 上获取客户端库:

通过 LibMan 安装

LibMan 可用于从 CDN托管的客户端库安装特定的客户端库文件。 例如,仅将缩小的 JavaScript 文件添加到项目。 有关该方法的详细信息,请参阅添加 SignalR 客户端库

连接到中心

以下代码创建并启动连接。 该中心的名称不区分大小写:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

跨源连接

通常,浏览器从与请求页面相同的域加载连接。 但是,有时需要连接到另一个域。

Important

客户端代码必须使用绝对 URL 而不是相对 URL。 将 .withUrl("/chathub") 更改为 .withUrl("https://myappurl/chathub")

为了防止恶意站点从另一站点读取敏感数据,默认情况下禁用跨域连接。 若要允许跨域请求,请在 Startup 类 中启用它:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

从客户端调用中心方法

JavaScript 客户端通过 HubConnectioninvoke 方法在中心调用公共方法。 invoke 方法接受:

  • 中心方法的名称。
  • 中心方法中定义的任何参数。

在下面的示例中,中心的方法名称为 SendMessage。 传递到 invoke 的第二个和第三个参数映射到中心方法的 usermessage 参数:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Note

只有在默认模式下使用 Azure SignalR 服务时才支持从客户端调用中心方法。 有关详细信息,请参阅常见问题解答(azure-signalr GitHub 存储库)。

invoke 方法返回 JavaScript Promise。 当服务器上的方法返回时,Promise 将以返回值(如有)完成。 如果服务器上的方法引发错误,Promise 会被拒绝并显示错误消息。 使用 asyncawaitPromisethencatch 方法来处理这些情况。

JavaScript 客户端还可以通过 HubConnectionsend 方法调用中心上的公共方法。 与 invoke 方法不同,send 方法不会等待来自服务器的响应。 send 方法返回 JavaScript Promise。 当消息已发送到服务器时,Promise 就会被解析。 如果发送消息时出错,Promise 会被拒绝并显示错误消息。 使用 asyncawaitPromisethencatch 方法来处理这些情况。

Note

使用 send 不会等待至服务器收到消息。 因此,无法从服务器返回数据或错误。

从中心调用客户端方法

若要接收来自中心的消息,请使用 HubConnectionon 方法定义一个方法。

  • JavaScript 客户端方法的名称。
  • 中心传递给方法的参数。

在下面的示例中,方法名称为 ReceiveMessage。 参数名称为 usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

connection.on 中的上述代码在服务器端代码使用 SendAsync 方法调用它时运行:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR 通过匹配在 SendAsyncconnection.on 中定义的方法名称和参数来确定要调用的客户端方法。

Note

最佳实践是,在 on 之后对 HubConnection 调用 start 方法。 这样做可确保在收到任何消息之前,处理程序已完成注册。

错误处理和日志记录

trycatchasyncawait 配合使用或使用 Promisecatch 方法处理客户端错误。 使用 console.error 将错误输出到浏览器的控制台:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

在建立连接时,通过传递记录器和要记录的事件类型来设置客户端日志跟踪。 使用指定的日志级别和更高级别记录消息。 可用的日志级别如下所示:

  • signalR.LogLevel.Error:错误消息。 仅记录 Error 消息。
  • signalR.LogLevel.Warning:有关潜在错误的警告消息。 记录 WarningError 消息。
  • signalR.LogLevel.Information:无错误的状态消息。 记录 InformationWarningError 消息。
  • signalR.LogLevel.Trace: 跟踪消息。 记录所有内容,包括在中心和客户端之间传输的数据。

使用 HubConnectionBuilder 上的 configureLogging 方法配置日志级别。 消息将记录到浏览器控制台:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

重新连接客户端

自动重新连接

可以将 SignalR 的 JavaScript 客户端配置为使用 HubConnectionBuilder 上的 withAutomaticReconnect 方法自动重连。 默认情况下,它不会自动重新连接。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

在没有任何参数的情况下,withAutomaticReconnect() 将客户端配置为在每次尝试重新连接之前分别等待 0、2、10 和 30 秒,在四次尝试失败后停止。

在开始尝试任何重新连接之前,HubConnection 将转换为 HubConnectionState.Reconnecting 状态并触发其 onreconnecting 回调,而不是像未配置自动重新连接的 Disconnected 一样转换为 onclose 状态并触发其 HubConnection 回调。 这样就可警告用户连接已丢失,并提醒其禁用 UI 元素。

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

如果客户端在其前四次尝试中成功重新连接,则 HubConnection 将转换回 Connected 状态并触发其 onreconnected 回调。 这样就可通知用户已重新建立连接。

由于连接对服务器来说是全新的,因此将为 connectionId 回调提供一个新的 onreconnected

Warning

如果 HubConnection 被配置为 skip negotiation,则 onreconnected 回调的 connectionId 参数将为 undefined。

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect() 不会将 HubConnection 配置为重试初始启动失败,因此需要手动处理启动失败:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

如果客户端在其前四次尝试中未成功重新连接,则 HubConnection 将转换为 Disconnected 状态并触发其 onclose 回调。 这样就可以通知用户连接已永久丢失并建议刷新页面:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

若要在断开连接或更改重新连接时间安排之前配置自定义的重新连接尝试次数,withAutomaticReconnect 会接受一个数字数组,表示在每次开始尝试重新连接之前要等待的延迟(以毫秒为单位)。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

前面的示例将 HubConnection 配置为在连接丢失后立即开始尝试重新连接。 这也适用于默认配置。

如果第一次重新连接尝试失败,则第二次重新连接尝试还会立即启动,而不是像在默认配置中一样等待 2 秒钟。

如果第二次重新连接尝试失败,则第三次重新连接尝试将在 10 秒后启动,这与默认配置相同。

然后,自定义行为同样不同于默认行为,前者会在第三次重新连接尝试失败后停止,而不是像在默认配置中一样在 30 秒后尝试重新连接。

如果想要更好地控制自动重新连接尝试的时间安排和次数,则 withAutomaticReconnect 需接受实现 IRetryPolicy 接口的对象,该对象具有一个名为 nextRetryDelayInMilliseconds 的方法。

nextRetryDelayInMilliseconds 接受一个 RetryContext 类型的参数。 RetryContext 具有三个属性:previousRetryCountelapsedMillisecondsretryReason,分别为 numbernumberError。 在尝试第一次重新连接之前,previousRetryCountelapsedMilliseconds 将为零,retryReason 将是导致连接丢失的错误。 在每次重试失败后,previousRetryCount 的数量将增加一个,并更新 elapsedMilliseconds 以反映截止目前重新连接所花的时间(以毫秒为单位),retryReason 将是导致最后一个重新连接尝试失败的错误。

nextRetryDelayInMilliseconds 必须返回一个数字,表示下次重连尝试前需要等待的毫秒数;如果 HubConnection 应停止重连,则返回 null

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

或者,你可以编写将手动重新连接客户端的代码,如手动重新连接中所述。

手动重新连接

下面的代码演示典型的手动重新连接方法:

  1. 创建函数(在本例中为 start 函数)以启动连接。
  2. 在连接的 start 事件处理程序中调用 onclose 函数。
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

生产环境中的实现通常使用指数退避,或按指定次数重试。

浏览器睡眠选项卡

某些浏览器具有选项卡冻结或休眠功能,用于减少非活动选项卡的计算机资源使用量。 这可能会导致 SignalR 连接关闭,并可能导致不必要的用户体验。 浏览器会使用启发式规则来判断某个标签页是否应被置于休眠状态,例如:

  • 播放音频
  • 持有 Web 锁
  • 持有 IndexedDB
  • 正在连接到 USB 设备
  • 采集视频或音频
  • 正在镜像中
  • 捕获窗口或屏幕

Note

这些启发式规则可能会随时间发生变化,也可能因浏览器而异。 查看支持矩阵,找出最适合你的方案的方法。

若要避免使应用进入休眠状态,应用应触发浏览器采用的某种启发式判断机制。

下面的代码示例演示如何使用 Web Lock 将选项卡保持为唤醒状态,并避免意外的连接关闭。

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

对于上述代码示例:

  • Web Lock 是实验性的。 条件检查可确认浏览器是否支持 Web Lock。
  • 会存储 Promise 解析器 (lockResolver),以便在允许标签页休眠时释放锁。
  • 关闭连接时,通过调用 lockResolver() 释放锁定。 释放锁定时,选项卡可进入睡眠状态。

其他资源