目录
[一、SignaIR 的介绍](#一、SignaIR 的介绍)
[1.1、SignaIR 的协议类型](#1.1、SignaIR 的协议类型)
[1.2、SignaIR 的使用场景](#1.2、SignaIR 的使用场景)
[二、SignaIR 的使用](#二、SignaIR 的使用)
[2.1.1、安装 SignaIR 包](#2.1.1、安装 SignaIR 包)
[2.1.2、住入 SignaIR 依赖](#2.1.2、住入 SignaIR 依赖)
[2.1.3、创建 SignaIR 核心组件](#2.1.3、创建 SignaIR 核心组件)
[2.2.1、安装 SignaIR 库](#2.2.1、安装 SignaIR 库)
[2.2.2、引入 SignaIR 库](#2.2.2、引入 SignaIR 库)
[2.2.3、使用 SignaIR 库创建长连接](#2.2.3、使用 SignaIR 库创建长连接)
一、SignaIR 的介绍
SignaIR 是一个可跨平台的开源库,让客户端与服务器的通信变得简单,它允许服务器实时推送数据到客户端,使得客户端能够及时地收到服务器的数据并呈现给用户。
1.1、SignaIR 的协议类型
WebSockets:是一种在单个TCP连接上进行全双工通信的协议,使得服务器和浏览器的通信更加简单,服务端可以主动发送信息。在建立连接后,服务器向客户端返回数据后连接不会断,会一直保持连接。
Server-Sent Events :简称 SSE,是基于 HTTP协议的。客户端通过发送一个特殊的 HTTP GET请求到服务器,
请求中包含 Accept: text/event-stream 头,表明客户端期望接收 SSE 数据流,服务器响应后并建立了连接,会保持连接打开,服务器可以持续向客户端推送数据(数据是由一系列事件组成)。SSE 是单向通信的,只能由服务器向客户端发送数据。
Long Polling(长轮询) :客户端会发送一个 HTTP 请求,服务器不会立即返回响应,一直保持连接,直到有数据更行时才返回响应,这样减少了每次请求的次数。如果长时间没有数据。则会超出设置的超时时间,可以在超时前返回一个 null,让前端在次轮询。
SignalR会自动选择服务器和客户端能力范围内的最佳通信方式,当然也可以手动指定。
1.2、SignaIR 的使用场景
SignaIR 的通信方式是为了能够及时地发现服务器有数据更新并展示给用户,一般用于在 网络聊天、实时状态更新、报警器、消息提醒、协作应用、游戏、看板等业务场景。
二、SignaIR 的使用
在 .net 6 + vue3 的项目中使用 SignaIR 进行实时通信,需要在 .net 6、vue3中都引入 SignaIR 库。
2.1、后端
2.1.1、安装 SignaIR 包
在 NUGET 包管理器中下载 SignaIR 包。
Microsoft.AspNet.SignalR
2.1.2、住入 SignaIR 依赖
在 Program.cs文件中注入依赖。
builder.Services.AddSignalR();
2.1.3、创建 SignaIR 核心组件
新建 Hubs文件夹,在文件夹中添加命名以 Hub结尾的类,用于处理客户端与服务器的通信。
public class GDPDateHub:Hub
{
private readonly AppDbContext _context;
public GDPDateHub(AppDbContext context)
{
_context = context;
}
/// <summary>
/// 获取数据,用于第一次向客户端发送数据
/// </summary>
/// <returns></returns>
public async Task GetGDPDate()
{
var gdpDate = await _context.GDPDatas.ToListAsync();
// 定义一个消息名称 ReceiveGDPDate
await Clients.All.SendAsync("ReceiveGDPDate", gdpDate);
}
}
使用 SignaIR与客户端进行通信时,与传统的 webapi接口的通信方式不同,SignaIR 会将数据从 SignaIR的核心组件 Hub 中通过请求管道返回给客户端,所以我们还需要在请求管道中注入核心组件的 SignaIR 依赖。
在 Program.cs文件中注入核心组件的 SignaIR 依赖。
// GDPDateHub:核心组件类型
// "/gdphub" :核心组件的路由,可以把核心组件看作是一个 HTTP GET请求的 API 接口,客户端是通过这个路由来找到核心组件处理业务逻辑的。
app.MapHub<GDPDateHub>("/gdphub");
有同学可能会问,如果我们有多个接口需要做实时通信怎么办?那还不好说吗,做添加几个核心组件,然后给每个核心组件注入核心组件的 SignaIR 依赖。
还有同学要问,那如果我需要给这个请求添加认证和权限分配怎么办?这个问题我下期再讲,也可以学习: SignalR:身份认证
2.1.4、数据更新触发服务器数据推送到客户端
在新增、修改和删除数据的接口中使用 _hubContext.Clients.All.SendAsync 来触发推送数据更新,SendAsync方法接收两个参数,第一个参数是触发消息推送的消息名称(与核心组件中的一致),第二个参数是推送到前端的数据。
[Route("api/[controller]")]
[ApiController]
public class GDPDataController : ControllerBase
{
private readonly AppDbContext _context;
private readonly IGDPDataRepository _dataRepository;
private readonly IHubContext<GDPDateHub> _hubContext;
public GDPDataController(AppDbContext context, IGDPDataRepository dataRepository, IHubContext<GDPDateHub> gDPDataRepository)
{
_context = context;
_dataRepository = dataRepository;
_hubContext = gDPDataRepository;
}
[HttpPost]
public async Task<IActionResult> AddGDPDate([FromBody] GDPData gdpDate)
{
if(gdpDate == null)
{
return BadRequest("参数错误");
}
_context.GDPDatas.Add(gdpDate);
await _context.SaveChangesAsync();
// 触发向客户端推送数据,
// ReceiveGDPDate:推送的消息名称,用来识别处理那个核心组件
// await _dataRepository.GetGDPDatasAsync():推送的数据,我这里推送的是所有数据,可以根据业务需求推送最新数据。
await _hubContext.Clients.All.SendAsync("ReceiveGDPDate",await _dataRepository.GetGDPDatasAsync());
return Ok("创建成功");
}
}
2.2、前端
在vue3中使用 @microsoft/signalr 库创建 BubConnection 对象后, BubConnection 对象中的常用连接操作方法。
|--------|------------------|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| 功能 | 方法 | 描述 | 案例 |
| 连接管理方法 | stop() | 用于停止当前SignaIR连接。调用该方法后,连接会进入Disconnecting状态,最终变为Disconnected状态。 | await connection.stop(); |
| 消息发送方法 | invoke() | 用于调用服务器端 Hub 类中的方法,并可以传递参数。该方法返回一个Promise,可以使用await关键字等待服务器的响应。 | await connection.invoke("方法名","参数值"); |
| 消息发送方法 | send() | 同样用于调用服务器后端 Hub 类中的方法,但不等待服务器的响应。返回一个Promise,当消息成功发送到服务器时,Promise会被解决。 | await connection.send("方法名","参数值"); |
| 消息接收方法 | on() | 用于监听服务器端发送的特定消息。当服务器调用Clients.All.SendAsync等待方法消息时,客户端可以通过on方法监听该消息,并在接收到消息时执行响应的回调函数。 | connection.on(" ReceiveMessage ",(data) => {console.log("收到消息:", data);}); |
| 事件监听方法 | onreconnecting() | 用于监听连接重新建立的过程。当连接断开并重新连接时,会触发该事件的回调函数。 | connection.onreconnecting((err) => {console.log("正在连接,错误信息:",err);}); |
| 事件监听方法 | onreconnected() | 用于监听连接重新建立成功的事件。当连接重新建立成功后,会触发该事件的回调函数。 | connection.onreconnected((connectionId) => {console.log("重新连接成功,新连接ID:", onnectionId)}); |
| 事件监听方法 | onclose() | 用于监听连接关闭的事件。当连接关闭时,会触发该事件的回调函数。 | connection.onclose((err) => {console.log("连接已关闭,错误信息:", err);}); |
[常用连接方法]
2.2.1、安装 SignaIR 库
cnpm install @microsoft/signalr
2.2.2、引入 SignaIR 库
import * as signaIR from '@microsoft/signalr';
2.2.3、使用 SignaIR 库创建长连接
指定使用 websocket 协议来建立长连接。我这里使用类型协商的方式会报错,提示协商失败,我就给它指定的使用 websocket 协议来建立长连接,如果想要通过类型协商的方式来创建连接,请看下面。
// 创建连接
// withUrl() 指定要连接的后端 SignaIR Hub 的URL。
// build() 构建返回一个 HubConnection 对象。
const connection = new signaIR.HubConnectionBuilder()
.withUrl("http://localhost:5251/gdphub", {
skipNegotiation: true, // 忽略协商,直接使用 WebSocket
transport: signaIR.HttpTransportType.WebSockets
})
.build();
const getGDPDates = async () => {
var result;
try {
// connection.start() 启动与后端的连接
await connection.start();
if (connection.state === signaIR.HubConnectionState.Connected) {
// connection.on 监听服务器发送的 ReceiveGDPDate 消息,并在连接收到消息时执行相应的回调函数。
// 注册接收消息事件,ReceiveGDPDate:服务端推送消息的方法名,必须与服务器端的核心组件中的消息名一致。
connection.on('ReceiveGDPDate', (data) => {
result = data;
});
}
// 调用服务器中的 Hub 核心组件的 GetGDPDate 方法
await connection.invoke("GetGDPDate"); // GetGDPDate:服务端方法名,必须与服务器端的核心组件中的方法名一致。
} catch (error) {
console.log(error);
}
return result;
};
不指定 SignaIR 的通信类型,通过类型协商的方法来常见连接。
// 创建连接
const connection = new signaIR.HubConnectionBuilder()
.withUrl("http://localhost:5251/gdphub")
.build();
三、遇到的问题及解决方法
遇到问题及解决方法的参考文章:.NET 6使用 SignaIR 遇到的问题及解决方法
这里我主要介绍如何查看 websocket 是否连接成功。
枚举值 | 数值 | 状态 |
---|---|---|
Disconnected | 0 | 连接已经关闭,处于关闭状态 |
Connecting | 1 | 正在建立连接 |
Connected | 2 | 连接已建立,可以进行通信 |
Reconnecting | 3 | 正在重新建立连接 |
Disconnecting | 4 | 连接正在进行关闭 |
[判断依据] |
**方法一:**在前端的控制台中输入:new WebSocket(地址)
可以查看地址是否正确。

看到图中返回的信息,url 中有个 ws,这个 ws又是什么鬼东西?没见过?
Websocket使用 ws 或 wss 的统一资源标志符,类似于 HTTP 或 HTTPS ,其中 wss 表示在 TLS 之上的 Websocket ,相当于 HTTPS 了。如:
ws:localhost:5251/gdphub
wss:localhost:5251/gdphub
默认情况下,Websocket 的 ws 协议使用 80 端口;运行在TLS之上时,wss 协议默认使用 443 端口。其实说白了,wss 就是 ws 基于 SSL 的安全传输,与 HTTPS 一样样的道理。
如果你的网站是 HTTPS 协议的,那你就不能使用 ws了,浏览器会 block 掉连接,和 HTTPS 下不允许 HTTP 请求一样,这时候我们就需要使用 wss了。
**方法二:**在 await connection.start() 后面打印 connection.state。
await connection.start();
console.log("SignalR 连接已建立");
console.log(connection.state);

好记性不如烂笔头,在学习的路上留下痕迹。希望能给你带来帮助,也期待你的点赞和评论。
若有不足之处,还请斧正。