ASP.NET Core SignalR .NET 客户端

使用 ASP.NET Core SignalR .NET 客户端库,你可以从 SignalR .NET 应用与中心通信。

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

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

安装 SignalR .NET 客户端包

Microsoft.AspNetCore。 SignalR.NET客户端需要客户端包才能连接到 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可以将 配置为使用 上的 方法 WithAutomaticReconnect 自动重新连接 HubConnectionBuilder 。 默认情况下,它不会自动重新连接。

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 事件。 这为用户提供了通知用户已重新建立连接并取消排队消息的排队的机会。

由于连接在服务器上看起来是全新的,因此 ConnectionId 将向事件处理程序提供一个新的 Reconnected

警告

Reconnected connectionId 如果 HubConnection 配置为跳过协商,事件处理程序的参数将为 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 采用类型为 的单个参数 RetryContextRetryContext具有三个属性 PreviousRetryCount ElapsedTime :、 RetryReason 和 ,它们分别是 long 、 和 TimeSpan Exception 。 在首次重新连接尝试之前, 和 都将为零,并且 将是导致连接丢失 PreviousRetryCount ElapsedTime RetryReason 的异常。 每次重试尝试失败后, 将递增 1,将更新以反映到目前为止重新连接所花费的时间,并且 将是导致上次重新连接尝试失败的 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 之前,的 .NET SignalR 客户端不会自动重新连接。 必须编写将手动重新连接客户端的代码。

使用 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 语法处理发送错误。

备注

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

错误处理和日志记录

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

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

其他资源