SignalR 实时通讯

  • ​​1.SignalR​​
  • ​​1.1.SignalR 简介​​
  • ​​1.2.SignalR 功能​​
  • ​​1.3.传输​​
  • ​​1.4.中心​​
  • ​​2.服务器​​
  • ​​2.1.配置中心​​
  • ​​2.2.上下文对象​​
  • ​​2.3.客户端对象​​
  • ​​2.4.创建​​
  • ​​2.5.中心功能实现​​
  • ​​4.客户端​​
  • ​​6.案例演示(DotNet 客户端)​​

1.SignalR

1.1.SignalR 简介

SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。

SignalR 提供用于创建服务器到客户端远程过程调用 (RPC) 的 API。 RPC 从服务器端 .NET Core 代码调用客户端上的函数。 提供多个受支持的平台,其中每个平台都有各自的客户端 SDK。

与 ASP.NET 的其余部分一样,SignalR 是为实现高性能而构建的,也是市面上最快的实时框架之一。

跨服务器横向扩展,内置支持使用 Redis、SQL Server 或 Azure 服务总线来协调每个实例之间的消息。

1.2.SignalR 功能

  • 自动处理连接管理。
  • 同时向所有连接的客户端发送消息。 例如聊天室。
  • 向特定客户端或客户端组发送消息。
  • 对其进行缩放,以处理不断增加的流量。
  • SignalR 中心协议

1.3.传输

SignalR 支持以下用于处理实时通信的技术(优先选择 WebSockets):

  • WebSockets ⬇
  • Server-Sent Events ⬇
  • 长轮询

1.4.中心

SignalR 使用 中心 在客户端和服务器之间进行通信。

Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。 可以将强类型参数传递给方法,从而支持模型绑定。 SignalR 提供两个内置中心协议:基于 JSON 的文本协议和基于 ​​MessagePack​​​ 的二进制协议。 与 ON 相比 JS,MessagePack 通常会创建较小的消息。 旧版浏览器必须支持 ​​XHR 级别 2​​ 才能提供 MessagePack 协议支持。

  • 中心通过发送包含客户端方法的名称和参数的消息来调用客户端代码。
  • 作为方法参数发送的对象使用配置的协议进行反序列化。
  • 客户端尝试将名称与客户端代码中的方法匹配。
  • 当客户端找到匹配项时,它会调用该方法并将反序列化的参数数据传递给它。

2.服务器

2.1.配置中心

中心 SignalR API 使连接的客户端能够在服务器上调用方法。 服务器定义从客户端调用的方法,客户端定义从服务器调用的方法。 SignalR 处理实现实时客户端到服务器和服务器到客户端通信所需的一切。

在 Program 中注册配置 SignalR 服务和 终结点

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR(); // 注册 SignalR 服务
app.MapRazorPages();
app.MapHub<ChatHub>("/Chat"); // 配置 SignalR 终结点

app.Run();

2.2.上下文对象

  • 类 Hub 包含一个 Context 属性,该属性包含以下 属性方法

属性

说明

ConnectionId

获取连接的唯一 ID(由 SignalR 分配)。 每个连接都有一个连接 ID。

UserIdentifier

获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ​​ClaimsPrincipal​​​ 中的 ​​ClaimTypes.NameIdentifier​​ 作为用户标识符。

User

获取与当前用户关联的 ​​ClaimsPrincipal​​。

Items

获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。

Features

获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。

ConnectionAborted

获取一个 ​​CancellationToken​​,它会在连接中止时发出通知。

方法

说明

GetHttpContext

返回 ​​HttpContext​​ 连接的 ;如果连接未与 HTTP 请求关联, null 则返回 。 对于 HTTP 连接,请使用此方法获取 HTTP 标头和查询字符串等信息。

Abort

中止连接。

2.3.客户端对象

  • 类 Hub 包含一个 Clients 属性,该属性包含以下用于 服务器和客户端之间通信属性方法

属性

说明

All

对所有连接的客户端调用方法

Caller

对调用了中心方法的客户端调用方法

Others

对所有连接的客户端调用方法(调用了方法的客户端除外)

方法

说明

AllExcept

对所有连接的客户端调用方法(指定连接除外)

Client

对连接的一个特定客户端调用方法

Clients

对连接的多个特定客户端调用方法

Group

对指定组中的所有连接调用方法

GroupExcept

对指定组中的所有连接调用方法(指定连接除外)

Groups

对多个连接组调用方法

OthersInGroup

对一个连接组调用方法(不包括调用了中心方法的客户端)

User

对与一个特定用户关联的所有连接调用方法

Users

对与多个指定用户关联的所有连接调用方法

表中的每个属性或方法都返回具有 ​​SendAsync​​​ 方法的对象。 方法 ​​SendAsync​​ 接收要调用的客户端方法的名称和任何参数。

和 方法返回 Client 的对象还包含方法 ​​InvokeAsync​​​,该方法可用于等待​​来自客户端的结果​​​。​​Caller​

2.4.创建

中心继承 Hub 类,可实现 弱类型中心强类型中心,这里只介绍强类型中心。

创建 IChatClient interface

public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}

实现强类型中心,Hub 类型为我们创建的 IChatClient interface

public class ChatHub : Hub<IChatClient>{}

2.5.中心功能实现

  • 向客户端发送消息
#region 连接处理

/// <summary>
/// 在连接上时
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
await SendMessageToCaller(Context.ConnectionId, "主服务连接成功!");
}

/// <summary>
/// 断开连接时
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override Task OnDisconnectedAsync(Exception? exception)
{
return base.OnDisconnectedAsync(exception);
}

#endregion

#region 消息发送

/// <summary>
/// 将消息发送到所有连接的客户端,不排除自己
/// </summary>
/// <param name="user"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 Clients.All 将消息发送到所有连接的客户端
/// </remarks>
/// <returns></returns>
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}

/// <summary>
/// 将消息发送到所有连接的客户端,排除自己
/// </summary>
/// <param name="user"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 Others 将消息发送到所有连接的客户端,排除自己
/// </remarks>
/// <returns></returns>
public async Task SendMessageToOthers(string user, string message)
{
await Clients.Others.ReceiveMessage(user, message);
}

/// <summary>
/// 将消息发送到指定连接的客户端
/// </summary>
/// <param name="user1"></param>
/// <param name="user2"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 User 将消息发送到指定连接的客户端
/// </remarks>
/// <returns></returns>
public async Task SendMessageToUser(string user1, string user2, string message)
{
await Clients.User(user1).ReceiveMessage(user2, message);
}

/// <summary>
/// 将消息发送回调用方
/// </summary>
/// <param name="user"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 Caller 将消息发送回调用方
/// </remarks>
/// <returns></returns>
public async Task SendMessageToCaller(string user, string message)
{
await Clients.Caller.ReceiveMessage(user, message);
}

/// <summary>
/// 将消息发送给 GroupName 组中的所有客户端
/// </summary>
/// <param name="GroupName"></param>
/// <param name="user"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 Group 将消息发送给 GroupName 组中的所有客户端
/// </remarks>
/// <returns></returns>
public async Task SendMessageToGroup(string GroupName, string user, string message)
{
await Clients.Group(GroupName).ReceiveMessage(user, message);
}

/// <summary>
/// 将消息发送给 GroupName 组中的所有客户端,排除自己
/// </summary>
/// <param name="GroupName"></param>
/// <param name="user"></param>
/// <param name="message"></param>
/// <remarks>
/// 使用 Group 将消息发送给 GroupName 组中的所有客户端,排除自己
/// </remarks>
/// <returns></returns>
public async Task SendMessageToOthersInGroup(string GroupName, string user, string message)
{
await Clients.OthersInGroup(GroupName).ReceiveMessage(user, message);
}
#endregion

4.客户端

  • JavaScript 客户端

Internet Explorer 和其他旧版浏览器,当前版本及更高版本指的是浏览器的最新版本。

  • DotNet 客户端

.NET 客户端可在 ASP.NET Core 支持的任何平台上运行,具体版本号参考微软文档说明。

  • Java 客户端

需 Java 8 或更高版本。

6.案例演示(DotNet 客户端)

使用 WPF 客户端程序

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100*" MinHeight="120" />
<RowDefinition Height="auto" />
<RowDefinition Height="40*" MinHeight="130" />
</Grid.RowDefinitions>

<ListBox
Name="listMessage"
Background="{StaticResource DominantColor}"
BorderThickness="0"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Message}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

<GridSplitter
Grid.Row="1"
Height="1"
HorizontalAlignment="Stretch"
Background="{StaticResource BorderColor}" />

<Grid Grid.Row="2" Margin="30,0">
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="64" />
</Grid.RowDefinitions>
<TextBox
x:Name="TxtNewContent"
Grid.Row="0"
Style="{StaticResource InputStyle}" />
<Button
Grid.Row="1"
Click="Send_Click"
Content="发送(S)"
Style="{StaticResource SendButtonStyle}" />
</Grid>
</Grid>
</Grid>
/// <summary>
/// 连接中心
/// </summary>
private readonly HubConnection connection;

/// <summary>
/// 连接字符串
/// </summary>
readonly string connectionStr = "https://localhost:7067/chat";

/// <summary>
/// 消息通知
/// </summary>
ObservableCollection<MessageEntity> messages = new ObservableCollection<MessageEntity>();

public MainWindow()
{
InitializeComponent();

connection = new HubConnectionBuilder().WithUrl(connectionStr).WithAutomaticReconnect().Build();

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

listMessage.ItemsSource = messages;

Loaded += MainWindow_Loaded;
}

private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Dispatcher.Invoke(() =>
{
messages.Add(new MessageEntity
{
Message = message,
IsSend = false,
});
});
});

await StartReceiveMessage();
}

private async Task StartReceiveMessage()
{
try
{
await connection.StartAsync();
userId = connection.ConnectionId;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Send_Click(object sender, RoutedEventArgs e)
{
try
{
await connection.InvokeAsync("SendMessageToOthers", userId, TxtNewContent.Text.Trim());
TxtNewContent.Text = string.Empty;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

public class MessageEntity
{
/// <summary>
/// 消息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 是否发送人
/// </summary>
public bool IsSend { get; set; }
}

SignalR 实时通讯_服务器


图中窗口使用 WindowChrome 控件自定义过,有需要可以去看​​【WPF】WindowChrome 自定义窗口完美实现​