.net 6 + vue3中使用SignaIR实现双向通信功能

目录

[一、SignaIR 的介绍](#一、SignaIR 的介绍)

[1.1、SignaIR 的协议类型](#1.1、SignaIR 的协议类型)

[1.2、SignaIR 的使用场景](#1.2、SignaIR 的使用场景)

[二、SignaIR 的使用](#二、SignaIR 的使用)

2.1、后端

[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.1.4、数据更新触发服务器数据推送到客户端

2.2、前端

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

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

若有不足之处,还请斧正。

相关推荐
qq. 28040339842 小时前
CSS层叠顺序
前端·css
Qwertyuiop20163 小时前
搭建开源笔记平台:outline
笔记·开源
喝拿铁写前端3 小时前
SmartField AI:让每个字段都找到归属!
前端·算法
猫猫不是喵喵.3 小时前
vue 路由
前端·javascript·vue.js
烛阴3 小时前
JavaScript Import/Export:告别混乱,拥抱模块化!
前端·javascript
bin91534 小时前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例12,TableView16_12 拖拽动画示例
前端·javascript·vue.js·ecmascript·deepseek
iOS技术狂热者4 小时前
使用抓包大师(sniff master)进行手机端iOS抓包的配置步骤
websocket·网络协议·tcp/ip·http·网络安全·https·udp
GISer_Jing4 小时前
[Html]overflow: auto 失效原因,flex 1却未设置min-height &overflow的几个属性以及应用场景
前端·html
程序员黄同学4 小时前
解释 Webpack 中的模块打包机制,如何配置 Webpack 进行项目构建?
前端·webpack·node.js
拉不动的猪4 小时前
vue自定义“权限控制”指令
前端·javascript·vue.js