ASP.NET Core SignalR JavaScript 客户端

作者:Rachel Appel

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

安装 SignalR 客户端包

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

通过 npm 安装

包管理器控制台运行以下命令:

npm init -y
npm install @microsoft/signalr

NPM 将包内容安装在 node_modules@microsoftsignalrdistbrowser 文件夹中。 创建 wwwroot/lib/signalr 文件夹。 将 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>

可在以下 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);
}

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

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

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

使用 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 方法调用它时运行:

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

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

  • 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 状态并像 HubConnection 一样触发其 onclose 回调。

重新连接方法提供了以下机会:

  • 警告用户连接已丢失。
  • 禁用 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 参数。

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 必须返回表示在下次重新连接尝试之前等待的毫秒数,或者在 null 应停止重新连接的情况下返回 HubConnection

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. 在连接的 onclose 事件处理程序中调用 start 函数。
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 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。
  • 存储承诺解析程序 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@microsoftsignalrdistbrowser 文件夹中。 在 wwwroot\lib 文件夹下新建名为 signalr 的文件夹。 将 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/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();

跨域连接

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

重要

客户端代码必须使用绝对 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);
}

注意

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

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

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

注意

使用 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 中定义的方法名称和参数来确定要调用的客户端方法。

注意

作为最佳做法,请在 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 回调,而不是像未配置自动重新连接的 HubConnection 一样转换为 Disconnected 状态并触发其 onclose 回调。 这样就可警告用户连接已丢失,并提醒其禁用 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 参数。

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 必须返回表示在下次重新连接尝试之前等待的毫秒数,或者在 null 应停止重新连接的情况下返回 HubConnection

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. 在连接的 onclose 事件处理程序中调用 start 函数。
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 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。
  • 存储承诺解析程序 (lockResolver),以便在选项卡可进入睡眠状态时释放锁定。
  • 关闭连接时,通过调用 lockResolver() 释放锁定。 释放锁定时,选项卡可进入睡眠状态。

其他资源