.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 设置为 true
  connection.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/540051.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

centos 7 sshd服务无法自动随机启动

centos 7 sshd 服务无法伴随主机启动而启动&#xff0c;而使用systemctl start sshd可以启动&#xff0c;很奇怪。 后来使用Kimi查询&#xff0c;有提示“检查系统启动服务的顺序和状态” systemctl list-dependencies <service>确保所有依赖服务都已正常启动。 查看本…

ArcGIS Desktop使用入门(三)图层右键工具——标注要素、将标注转换为注记

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

python基础——类【类的定义和使用、魔术方法】

&#x1f4dd;前言&#xff1a; python中的类&#xff0c;自我感觉在某种程度上和C语言的结构体是有共同之处的&#xff0c;如果有兴趣&#xff0c;可以先看看这篇文章&#xff1a;C语言——结构体类型&#xff08;一&#xff09;&#xff0c;先了解一下C语言中的结构体&#x…

使用springboot整合shiro进行登录认证(md5+盐值+散列次数)

准备工作&#xff1a; md5加密算法&#xff1a; public class md5Test {Testpublic void md5() {//明文(123) 盐值(monian) 加密次数(1024) > 密文 8ea680082c12d7d878a3a97214ebbdc2Md5Hash md5Hash new Md5Hash("123","monian",1024);System…

【蓝桥杯嵌入式】串口通信与RTC时钟

【蓝桥杯嵌入式】串口通信与RTC时钟 串口通信cubemx配置串口通信程序设计 RTC时钟cubemx配置程序设计 串口通信 cubemx配置 打开串口通信&#xff0c;并配置波特率为9600 打开串口中断 重定义串口接收与发送引脚&#xff0c;默认是PC4&#xff0c;PC5&#xff0c;需要改为P…

vue3 依赖-组件tablepage-vue3说明文档,列表页快速开发,使用思路及范例(Ⅳ)其他配置项

vue3 依赖-组件tablepage-vue3说明文档&#xff0c;列表页快速开发&#xff0c;使用思路及范例&#xff08;Ⅰ&#xff09;配置项文档 vue3 依赖-组件tablepage-vue3说明文档&#xff0c;列表页快速开发&#xff0c;使用思路及范例&#xff08;Ⅱ&#xff09;搜索及数据获取配…

STL函数对象

1&#xff0c;函数对象 1.1 函数对象概念 概念&#xff1a; 重载函数调用操作符的类&#xff0c;其对象常称为函数对象函数对象使用重载的&#xff08;&#xff09;时&#xff0c;行为类似函数调用&#xff0c;也称为仿函数 本质&#xff1a; 函数对象&#xff08;仿函数&…

国外站群服务器有哪几种?

国外站群服务器种类繁多&#xff0c;它们各具特色&#xff0c;适用于不同的业务需求和场景。以下将为您科普几种常见的国外站群服务器及其特点。 首先&#xff0c;美国站群服务器以其丰富的IP资源和强大的网络技术著称。作为全球网络技术和数据中心发展的领先者&#xff0c;美国…

僵尸进程和孤儿进程

目录 引言僵尸进程僵尸进程的状态僵尸进程周边知识 孤儿进程孤儿进程的状态 进程中的其他状态①.R---表示进程运行状态。②.S---表示进程的休眠状态。(进程什么都没做)③T 和 t 进程的运行、阻塞和挂起运行阻塞挂起状态&#xff1a; 引言 今天我们来将僵尸进程和孤儿进程以及其…

matlab使用教程(42)—常见的二维图像绘制方法

这个博客用于演示如何在 MATLAB 中创建曲线图、条形图、阶梯图、误差条形图、极坐标图、针状图、散点图。 1.曲线图 plot 函数用来创建 x 和 y 值的简单线图。 x 0:0.05:5; y sin(x.^2); figure plot(x,y) 运行结果&#xff1a; 线图可显示多组 x 和 y 数据。 x 0:0.05:…

如何正确使用数字化仪前端信号调理?(二)

在上期文章如何正确使用数字化仪前端信号调理&#xff1f;&#xff08;一&#xff09;中&#xff0c;我们为大家介绍了数字化仪前端电路所需的特性以及使用过程中需要的输入抗阻和输入耦合&#xff0c;本期文章将为您介绍数字化仪前端信号调理的使用过程中所需的输入电压范围&a…

一键开启Scrum回顾会议的精彩时刻

其实回顾会议作为一个检视、反馈、改进环节&#xff0c;不仅在传统的瀑布管理模式中&#xff0c;还是在Scrum一类的敏捷管理流程中&#xff0c;都是非常重要的活动。一些团队认为它无法产生直接的价值&#xff0c;所以有意忽略了这个会议&#xff1b;一些团队在越来越多的回顾中…

bilibili PC客户端架构设计——基于Electron

众所周知&#xff0c;bilibili是个学习的网站&#xff0c;网页端和粉版移动端都非常的好用&#xff0c;不过&#xff0c;相对其它平台来说bilibili的PC客户端也算是大器晚成了。在有些场景PC客户端的优势也是显而易见的&#xff0c;比如&#xff0c;跓留电脑桌面的快捷、独立的…

Redis搭建主从

Redis搭建主从: 1:拉取Redis镜像 docker pull redis2:创建主从对应的目录结构 3:对redis6379.log,redis6380.log,redis6381.log进行授权 chmod 777 redis6379.log chmod 777 redis6380.log chmod 777 redis6381.log4:修改主(master)的配置文件 5:创建主(master) redis_6379 …

二维数组---刷题

一维数组不想更了&#xff0c;弄点二维数组&#xff01; 1.对角线 已知一个6*6的矩阵&#xff0c;把矩阵两条对角线上的元素加上10&#xff0c;然后输出这个新矩阵。 思路 题目简单&#xff0c;6*636&#xff0c;可以得知有36个元素。数组就定义成a[7][7]&#xff0c;难点在与…

最前沿・量子退火建模方法(1) : subQUBO讲解和python实现

前言 量子退火机在小规模问题上的效果得到了有效验证&#xff0c;但是由于物理量子比特的大规模制备以及噪声的影响&#xff0c;还没有办法再大规模的场景下应用。 这时候就需要我们思考&#xff0c;如何通过软件的方法怎么样把大的问题分解成小的问题&#xff0c;以便通过现在…

哪些因素影响阻抗控制?网格铜的妙用

原文来自微信公众号&#xff1a;工程师看海&#xff0c;与我联系&#xff1a;chunhou0820 看海原创视频教程&#xff1a;《运放秘籍》 大家好&#xff0c;我是工程师看海&#xff0c;原创文章欢迎点赞分享&#xff01; 前文介绍了传输线、特性阻抗以及信号的反射概念&#xff…

【漏洞复现】用友 NC PaWfm SQL注入漏洞

0x01 产品简介 用友NC是用友网络科技股份有限公司开发的一款大型企业数字化平台。它主要用于企业的财务核算、成本管理、资金管理、固定资产管理、应收应付管理等方面的工作&#xff0c;致力于帮助企业建立科学的财务管理体系&#xff0c;提高财务核算的准确性和效率。 0x02 …

在线批量生成URL HTML单页网页程序

输入前缀、开始数字、结束数字、后缀 即可快速生成 几万、十万、百万 条链接。 支持 一键复制、 一键导出本地 txt 文件。 源码免费下载地址抄笔记 (chaobiji.cn)

Spring MVC应用分层(三层架构)

该片文章主要是对 Spring MVC应用分层&#xff08;三层架构&#xff09;进行简单的介绍和学习。 一、介绍 1、什么是应用分层 应用分层 是一种 软件开发设计思想 , 它将应用程序分成N个层次, 这N个层次分别负责各自的职责, 多个 层次之间协同提供完整的功能. 根据项目的复杂…