ASP.NET Core SignalR .NET 客户端

通过 ASP.NET Core SignalR .NET 客户端库可以从 .NET 应用与 SignalR 中心进行通信。

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

本文中的代码示例是使用 .ASP.NET Core SignalR .NET 客户端的 WPF 应用。

安装 SignalR .NET 客户端包

.NET 客户端需要 Microsoft.AspNetCore.SignalR.Client 包才能连接到 SignalR 中心。

若要安装该客户端库,请在“包管理器控制台”窗口中运行以下命令:

Install-Package Microsoft.AspNetCore.SignalR.Client

连接到中心

若要建立连接,请创建 HubConnectionBuilder 并调用 Build。 在建立连接期间,可以配置中心 URL、协议、传输类型、日志级别、标头和其他选项。 可通过将任何 HubConnectionBuilder 方法插入 Build 中来配置任何必需选项。 使用 StartAsync 启动连接。

using System;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.AspNetCore.SignalR.Client;

namespace SignalRChatClient
{
    public partial class MainWindow : Window
    {
        HubConnection connection;
        public MainWindow()
        {
            InitializeComponent();

            connection = new HubConnectionBuilder()
                .WithUrl("http://localhost:53353/ChatHub")
                .Build();

            connection.Closed += async (error) =>
            {
                await Task.Delay(new Random().Next(0,5) * 1000);
                await connection.StartAsync();
            };
        }

        private async void connectButton_Click(object sender, RoutedEventArgs e)
        {
            connection.On<string, string>("ReceiveMessage", (user, message) =>
            {
                this.Dispatcher.Invoke(() =>
                {
                   var newMessage = $"{user}: {message}";
                   messagesList.Items.Add(newMessage);
                });
            });

            try
            {
                await connection.StartAsync();
                messagesList.Items.Add("Connection started");
                connectButton.IsEnabled = false;
                sendButton.IsEnabled = true;
            }
            catch (Exception ex)
            {
                messagesList.Items.Add(ex.Message);
            }
        }

        private async void sendButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                await connection.InvokeAsync("SendMessage", 
                    userTextBox.Text, messageTextBox.Text);
            }
            catch (Exception ex)
            {                
                messagesList.Items.Add(ex.Message);                
            }
        }
    }
}

处理丢失的连接

自动重新连接

可以将 HubConnection 配置为对 HubConnectionBuilder 使用 WithAutomaticReconnect 方法来自动重新连接。 默认情况下,它不会自动重新连接。

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect()
    .Build();

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

在开始任何重新连接尝试之前,HubConnection 会转换为 HubConnectionState.Reconnecting 状态,并触发 Reconnecting 事件。 这样就可警告用户连接已丢失,并提醒其禁用 UI 元素。 非交互式应用可以开始排队或删除消息。

connection.Reconnecting += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Reconnecting);

    // Notify users the connection was lost and the client is reconnecting.
    // Start queuing or dropping messages.

    return Task.CompletedTask;
};

如果客户端在其前四次尝试中成功重新连接,则 HubConnection 将转换回 Connected 状态并触发 Reconnected 事件。 这样就可通知用户已重新建立连接并对任何已排队的消息取消排队。

由于连接对服务器来说是全新的,因此将为 Reconnected 事件处理程序提供一个新的 ConnectionId

警告

如果 HubConnection 已配置为跳过协商,则Reconnected 事件处理程序的 connectionId 参数会为 null。

connection.Reconnected += connectionId =>
{
    Debug.Assert(connection.State == HubConnectionState.Connected);

    // Notify users the connection was reestablished.
    // Start dequeuing messages queued while reconnecting if any.

    return Task.CompletedTask;
};

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

public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token)
{
    // Keep trying to until we can start or the token is canceled.
    while (true)
    {
        try
        {
            await connection.StartAsync(token);
            Debug.Assert(connection.State == HubConnectionState.Connected);
            return true;
        }
        catch when (token.IsCancellationRequested)
        {
            return false;
        }
        catch
        {
            // Failed to connect, trying again in 5000 ms.
            Debug.Assert(connection.State == HubConnectionState.Disconnected);
            await Task.Delay(5000);
        }
    }
}

如果客户端在其前四次尝试中未成功重新连接,则 HubConnection 将转换为 Disconnected 状态并触发 Closed 事件。 这样就可以尝试手动重启连接或通知用户连接已永久丢失。

connection.Closed += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Disconnected);

    // Notify users the connection has been closed or manually try to restart the connection.

    return Task.CompletedTask;
};

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

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
    .Build();

    // .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }) yields the default behavior.

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

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

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

自定义行为再次与默认行为不同,它会在第三次重新连接尝试失败后停止。 在默认配置中,会在接下来 30 秒过后再次尝试重新连接。

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

NextRetryDelay 采用一个 RetryContext 类型的参数。 RetryContext 具有三个属性:PreviousRetryCountElapsedTimeRetryReason,分别为 longTimeSpanException。 在尝试第一次重新连接之前,PreviousRetryCountElapsedTime 将为零,RetryReason 将是导致连接丢失的异常。 在每次重试失败后,PreviousRetryCount 的数量将增加一个,并更新 ElapsedTime 以反映截止目前重新连接所花的时间,RetryReason 将是导致最后一个重新连接尝试失败的异常。

NextRetryDelay 必须返回表示在下次重新连接尝试之前等待的时间的 TimeSpan,或者在 null 应停止重新连接的情况下返回 HubConnection

public class RandomRetryPolicy : IRetryPolicy
{
    private readonly Random _random = new Random();

    public TimeSpan? NextRetryDelay(RetryContext retryContext)
    {
        // If we've been reconnecting for less than 60 seconds so far,
        // wait between 0 and 10 seconds before the next reconnect attempt.
        if (retryContext.ElapsedTime < TimeSpan.FromSeconds(60))
        {
            return TimeSpan.FromSeconds(_random.NextDouble() * 10);
        }
        else
        {
            // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
            return null;
        }
    }
}
HubConnection connection = new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new RandomRetryPolicy())
    .Build();

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

手动重新连接

警告

在版本 3.0 之前,SignalR 的 .NET 客户端不会自动重新连接。 必须编写用于手动重新连接客户端的代码。

使用 Closed 事件可响应连接丢失。 例如,你可能要自动重新连接。

Closed 事件需要一个返回 Task 的委托,这使异步代码可在不使用 async void 的情况下运行。 若要在同步运行的 Closed 事件处理程序中满足委托签名,请返回 Task.CompletedTask

connection.Closed += (error) => {
    // Do your close logic.
    return Task.CompletedTask;
};

异步支持的主要原因是使你可以重启连接。 启动连接是一种异步操作。

在重启连接的 Closed 处理程序中,可考虑等待一些随机延迟以防止使服务器过载,如下面的示例中所示:

connection.Closed += async (error) =>
{
    await Task.Delay(new Random().Next(0,5) * 1000);
    await connection.StartAsync();
};

从客户端调用中心方法

InvokeAsync 会对中心调用方法。 将中心方法中定义的中心方法名称和所有参数传递给 InvokeAsync。 SignalR 是异步的,因此在进行调用时请使用 asyncawait

await connection.InvokeAsync("SendMessage", 
    userTextBox.Text, messageTextBox.Text);

InvokeAsync 方法会返回一个在服务器方法返回时完成的 Task。 返回值(如果有)作为 Task 的结果提供。 服务器上的方法所引发的任何异常都会产生出错的 Task。 使用 await 语法等待服务器方法完成,并使用 try...catch 语法处理错误。

SendAsync 方法会返回一个在消息已发送到服务器时完成的 Task。 不会提供返回值,因为此 Task 不会等到服务器方法完成。 发送消息期间在客户端上引发的任何异常都会产生出错的 Task。 使用 awaittry...catch 语法处理发送错误。

注意

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

从中心调用客户端方法

在生成之后,但是在启动连接之前使用 connection.On 定义中心调用的方法。

connection.On<string, string>("ReceiveMessage", (user, message) =>
{
    this.Dispatcher.Invoke(() =>
    {
       var newMessage = $"{user}: {message}";
       messagesList.Items.Add(newMessage);
    });
});

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

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

注意

虽然连接的中心端支持强类型消息,但客户端必须使用带有方法名称的泛型方法 HubConnection.On 进行注册。 有关示例,请参阅后台服务中的主机 ASP.NET Core SignalR

错误处理和日志记录

使用 try-catch 语句处理错误。 检查 Exception 对象,以确定发生错误后要执行的适当操作。

try
{
    await connection.InvokeAsync("SendMessage", 
        userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{                
    messagesList.Items.Add(ex.Message);                
}

其他资源