.NET SignalR Redis实时Web应用

2024-04-13 07:52
文章标签 应用 redis web 实时 net signalr

本文主要是介绍.NET SignalR Redis实时Web应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

环境 Win10 VS2022 .NET8 Docker  Redis

前言

什么是 SignalR?

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

适合 SignalR 的候选项:

  • 需要从服务器进行高频率更新的应用。 (游戏、社交网络、投票、拍卖、地图和 GPS 应用)
  • 仪表板和监视应用。 (公司仪表板、即时销售更新或出行警报)
  • 协作应用。 (包括白板应用和团队会议软件)
  • 需要通知的应用。( 社交网络、电子邮件、聊天、游戏等)

SignalR 提供用于创建服务器到客户端的远程过程调用 (RPC) API。 RPC 从服务器端 .NET Core 代码调用客户端上的函数。支持JavaScript ,.NET ,JAVA,Swift (官方没有明确支持,这是第三方库)其中每个平台都有各自的客户端 SDK。 因此,RPC 调用所调用的编程语言有所不同。

ASP.NET Core SignalR 的一些功能:

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

1.👋nuget引入SignalR

2.👀创建SignalR Hub

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;namespace WebSignalR
{public class ChatHub : Hub{public async Task SendMessage(string user, string message){await Clients.All.SendAsync("ReceiveMessage", user, message);}}}

3.🌱 Program.cs添加SignalR服务

 (Startup.cs)

//添加SignalR服务 
builder.Services.AddSignalR();
builder.Services.AddControllersWithViews();
app.UseEndpoints(endpoints =>
{endpoints.MapHub<ChatHub>("/chathub");endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");
});

4.📫 添加前端代码


<div class="text-center"><div id="chat-container"><input type="text" id="userInput" placeholder="Your name" /><input type="text" id="messageInput" placeholder="Type a message..." /><button id="sendButton">Send</button><ul id="messagesList"></ul></div><script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.12/signalr.min.js"></script><script>const connection = new signalR.HubConnectionBuilder().withUrl("/chathub").build();connection.on("ReceiveMessage", function (user, message) {const encodedUser = user.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");const encodedMessage = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");const li = document.createElement("li");li.textContent = `${encodedUser}: ${encodedMessage}`;document.getElementById("messagesList").appendChild(li);});connection.start().catch(function (err) {return console.error(err.toString());});document.getElementById("sendButton").addEventListener("click", function (event) {const user = document.getElementById("userInput").value;const message = document.getElementById("messageInput").value;connection.invoke("SendMessage", user, message).catch(function (err) {return console.error(err.toString());});event.preventDefault();});</script></div>

5.⚡F5运行

升级优化

封装Msg

    public class Msg{public string? user { get; set; }public string? message { get; set; }}

sendMessage

      public async Task SendMessage(Msg entity){if (Clients != null)await Clients.All.SendAsync("ReceiveMessage", entity.user, entity.message);// $"{entity.user} 发送消息:{entity.message}");}

前端   connection.invoke("SendMessage" ...   传递msg对象进来即可

6.💪跨域问题

builder.Services.AddCors(options =>
{options.AddPolicy("CorsPolicy",builder => builder.AllowAnyMethod().AllowAnyHeader().WithOrigins("http://localhost:5173") // 替换为你允许的来源.AllowCredentials());
});
//通过添加app.UseCors("CorsPolicy")中间件来启用跨域支持
app.UseCors("CorsPolicy"); 

上面代码中的WithOrigins方法指定了允许访问SignalR端点的来源。将​"http://localhost:5173"替换为你允许的实际来源。如果要允许任何来源访问,可以使用通配符"*"。​

这样就可以跨域访问 👇Vue跨域


 

7.🧙‍♂️聊天池的实现

实际生产可能需要1对1或者多对多,可在后端建立一个字典,将聊天池的标识映射到该聊天池的连接ID列表。

        public Dictionary<string, List<string>> _chatRooms = new Dictionary<string, List<string>>();public async Task JoinChatRoom(string chatRoomId){// 将用户连接添加到特定的聊天池if (!MsgSt._chatRooms2.ContainsKey(chatRoomId)){MsgSt._chatRooms2[chatRoomId] = new List<string>();}MsgSt._chatRooms2[chatRoomId].Add(Context.ConnectionId);// int i = _chatRooms.Count;Console.WriteLine("chatRoomId-Cid" + chatRoomId + " " + Context.ConnectionId);}public async Task SendMessageToChatRoom(string chatRoomId, string user, string message){//         Console.WriteLine(connectionIds);// 向特定的聊天池发送消息if (MsgSt._chatRooms2.TryGetValue(chatRoomId, out var connectionIds)){foreach (var connectionId in connectionIds){await Clients.Client(connectionId).SendAsync("ReceiveMessage",user, message);}}//  await Clients.Client(connectionId).SendAsync("ReceiveMessage", message);}
   public class MsgSt{public static Dictionary<string, List<string>> _chatRooms2= new Dictionary<string, List<string>>();//public static int temp2 = 0;}

在前端发送消息时,除了发送消息内容外,还要发送消息到的聊天池的标识。

JoinChatRoom   SendMessageToChatRoom

    <script>const connection = new signalR.HubConnectionBuilder().withUrl("/chathub").build();const userId = "userid001";const chatRoomId = "room001"; // 聊天池标识connection.on("ReceiveMessage", function (user, message) {const encodedUser = user.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");const encodedMessage = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");const li = document.createElement("li");li.textContent = `${encodedUser}: ${encodedMessage}`;document.getElementById("messagesList").appendChild(li);});connection.start().then(() => {console.log("Connection started" +chatRoomId);connection.invoke("JoinChatRoom", chatRoomId); // 加入特定的聊天池}).catch(err => console.error(err));document.getElementById("sendButton").addEventListener("click", function (event) {const message = document.getElementById("messageInput").value;const user = document.getElementById("userInput").value;connection.invoke("SendMessageToChatRoom", chatRoomId, user, message).catch(function (err) {return console.error(err.toString());});event.preventDefault();});</script>

chatroom1 


 

8.☔断线重连

确保客户端在与 SignalR Hub 的连接断开后能够重新连接并恢复之前的状态

可以在客户端代码中实现重连逻辑

  let isConnected = false; // 用于标识是否已连接// 连接成功时将 isConnected 设置为 trueconnection.onclose(() => {isConnected = false;});async function startConnection() {try {await connection.start();console.log("Connection started");isConnected = true;} catch (err) {console.error(err);isConnected = false;// 连接失败时尝试重新连接setTimeout(startConnection, 5000); // 5秒后重试}}startConnection(); // 初始连接

9.🌠配置Redis分布式缓存

Docker Redis 👈 Redis部署

用 Microsoft.Extensions.Caching.StackExchangeRedis 包连接到 Redis 并使用分布式缓存。这样可以确保即使服务重启,也能够保留聊天室的状态。

安装 Microsoft.Extensions.Caching.StackExchangeRedis   

or StackExchange.Redis 

Program.cs

// 添加Redis缓存
builder.Services.AddStackExchangeRedisCache(options =>
{options.Configuration = "127.0.0.1:6379"; // Redis服务器地址options.InstanceName = "ChatRooms"; // 实例名称
});

options.Configuration 设置为 Redis 服务器的地址,如果 Redis 运行在本地,则可以设置为 "localhost"。options.InstanceName 是 Redis 实例名称。

启动Redis服务  

在 ChatHub 中注入 IDistributedCache,连接到 Redis

_cache相当于 _chatRooms2存放连接ID的列表

private readonly IDistributedCache _cache;public ChatHub(IDistributedCache cache){_cache = cache;}public async Task JoinChatRoom(string chatRoomId){// 使用Redis的SET操作来添加连接ID到聊天室  var connectionId = Context.ConnectionId;var key = $"chatrooms:{chatRoomId}";var connectionIds = await _cache.GetStringAsync(key);var connectionsList = string.IsNullOrEmpty(connectionIds)? new List<string>(): connectionIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();connectionsList.Add(connectionId);await _cache.SetStringAsync(key, string.Join(",", connectionsList));Console.WriteLine($"chatRoomId-Cid {chatRoomId} {connectionId}");}public async Task SendMessageToChatRoom(string chatRoomId, string user, string message){var key = $"chatrooms:{chatRoomId}";var connectionIds = await _cache.GetStringAsync(key);if (!string.IsNullOrEmpty(connectionIds)){var connectionsList = connectionIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);foreach (var connectionId in connectionsList){await Clients.Client(connectionId).SendAsync("ReceiveMessage", user, message);}}}

这样前端传过来的 room001 room002 便会存入到Redis里面

运行调试的时候可以看到有用户JionChatRoom的chatRoomId connectionId

也可通过Redis命令 KEY * 查看

PS:这里用简单的字符串来存储连接ID的列表,连接ID之间用逗号分隔,实际生产可使用Redis的集合(Set)数据类型来存储连接ID,还需处理Redis连接失败、缓存过期等异常情况。

📜参考资料:

ASP.NET Core SignalR 入门 | Microsoft Learn

RPC-wiki

这篇关于.NET SignalR Redis实时Web应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/899561

相关文章

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

一文教你如何将maven项目转成web项目

《一文教你如何将maven项目转成web项目》在软件开发过程中,有时我们需要将一个普通的Maven项目转换为Web项目,以便能够部署到Web容器中运行,本文将详细介绍如何通过简单的步骤完成这一转换过程... 目录准备工作步骤一:修改​​pom.XML​​1.1 添加​​packaging​​标签1.2 添加

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

Redis 中的热点键和数据倾斜示例详解

《Redis中的热点键和数据倾斜示例详解》热点键是指在Redis中被频繁访问的特定键,这些键由于其高访问频率,可能导致Redis服务器的性能问题,尤其是在高并发场景下,本文给大家介绍Redis中的热... 目录Redis 中的热点键和数据倾斜热点键(Hot Key)定义特点应对策略示例数据倾斜(Data S

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

Redis中管道操作pipeline的实现

《Redis中管道操作pipeline的实现》RedisPipeline是一种优化客户端与服务器通信的技术,通过批量发送和接收命令减少网络往返次数,提高命令执行效率,本文就来介绍一下Redis中管道操... 目录什么是pipeline场景一:我要向Redis新增大批量的数据分批处理事务( MULTI/EXE

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、