码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 基于SqlSugar的开发框架循序渐进介绍(25)-- 基于SignalR实现多端的消息通讯


    基于ASP.NET Core SignalR 可以实现客户端和服务器之间进行即时通信。本篇随笔介绍一些SignalR的基础知识,以及结合对SqlSugar的开发框架的支持,实现SignalR的多端处理整合,从而实现Winform客户端,基于Vue3+ElementPlus的BS端整合,后面也可以实现对移动端的SignalR的整合通讯。

    适合 SignalR 的应用场景:

    • 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
    • 仪表板和监视应用。
    • 协作应用。 协作应用的示例包括白板应用和团队会议软件。
    • 需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。

    SignalR 自动选择服务器和客户端能力范围内的最佳传输方法,如WebSockets、Server-Sent Events、长轮询。Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。SignalR 提供两个内置中心协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。

    客户端负责通过 HubConnection 对象建立到服务器终结点的连接。 Hub 连接在每个目标平台中表示:

    • .NET 客户端:Microsoft.AspNetCore.SignalR.Client.HubConnection
    • JavaScript 客户端:@microsoft/signalr.HubConnection
    • Java 客户端:com.microsoft.signalr.HubConnection

    当中心连接实例成功启动后,消息可以自由地双向流动。 用户可以自由地将通知发送到服务器,以及从服务器接收通知。 客户端是任何已连接的应用程序,例如(但不限于)Web 浏览器、移动应用或桌面应用。

    1、SignalR服务端

    在.net core的Web API上,我们首先需要注册SignalR的服务,然后创建对应的Hub进行使用。一般可以在启动类中添加如下代码即可。

    复制代码
    builder.Services.AddSignalR();// 即时通讯
    
    app.UseEndpoints(endpoints =>
    {
        // 注册集线器
        endpoints.MapHub("/hubs/onlineUser");
    });
    复制代码

    定义集线器只需要继承 Hub 或 Hub 泛型基类即可。

    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
            => await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    泛型强类型方法是使用 Hub的强类型Hub类。在以下示例中 ChatHub ,客户端方法已提取到名为 的 IChatClient接口中:

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

    此接口可用于将前面的 ChatHub 示例重构为强类型:

    复制代码
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
            => await Clients.All.ReceiveMessage(user, message);
    
        public async Task SendMessageToCaller(string user, string message)
            => await Clients.Caller.ReceiveMessage(user, message);
    
        public async Task SendMessageToGroup(string user, string message)
            => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
    }
    复制代码

    这样Clients的对象都具备了接口定义的 ReceiveMessage方法调用,实际这个就是客户端的方法。

    使用 Hub 可以对客户端方法进行编译时检查。 这可以防止使用字符串引起的问题,因为 Hub 只能提供对 接口中定义的方法的访问权限。 使用强类型 Hub 会禁止使用 SendAsync。

    Hub服务端中心

    复制代码
    public interface IClient
    {
        Task<string> GetMessage();
    }
    
    public class ChatHub : Hub
    {
        public async Task<string> WaitForMessage(string connectionId)
        {
            string message = await Clients.Client(connectionId).GetMessage();
            return message;
        }
    }
    复制代码

    .NET 客户端

    客户端在其 .On(...) 处理程序中返回结果,如下所示:

    复制代码
    hubConnection.On("GetMessage", async () =>
    {
        Console.WriteLine("Enter message:");
        var message = await Console.In.ReadLineAsync();
        return message;
    });
    复制代码

    Typescript 客户端

    复制代码
    hubConnection.on("GetMessage", async () => {
        let promise = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("message");
            }, 100);
        });
        return promise;
    });
    复制代码

    Java 客户端

    hubConnection.onWithResult("GetMessage", () -> {
        return Single.just("message");
    });

    在框架中整合SignalR的Hub的时候,我们定义一个接口IOnlineUserHub,以便强类型对客户端接口方法的调用,减少错误。

    然后在定义一个Hub的对象类,如下所示 。

    复制代码
    public class OnlineUserHub : Hub
    {
            private readonly IOnlineUserService _onlineUserService;
            private readonly IHubContext _chatHubContext;
    
            public OnlineUserHub(IOnlineUserService onlineUserService,
                IHubContext onlineUserHubContext)
            {
                _onlineUserService = onlineUserService;
                _chatHubContext = onlineUserHubContext;
            }
    }
    复制代码

    对象Hub本身可以通过注入一个 IHubContext 接口来获得对它的调用,如上面构造函数所示。该Hub一般还需要重写连接和断开的处理操作,如下代码所示。

     如对于用户的SignalR连接发起,我们需要判断用户的令牌及相关身份信息,如果成功,则通过给客户端提供在线用户列表。

    复制代码
            /// 
            /// 连接后处理
            /// 
            /// 
            public override async Task OnConnectedAsync()
            {
                var httpContext = Context.GetHttpContext();
    
                var token = httpContext!.Request.Query["access_token"];
                if (string.IsNullOrWhiteSpace(token)) return;
    
                ................
    
                //向客户端提供在线用户信息
                await _chatHubContext.Clients.Groups(groupName).OnlineUserList(new OnlineUserList
                {
                    ConnectionId = user.ConnectionId,
                    RealName = user.RealName + $"({client.UA.Family})", //加上实际终端
                    Online = true,
                    UserList = userList.Items.ToList()
                });
    
                //更新在线用户缓存
                await RedisHelper.SetAsync(CacheConst.KeyOnlineUser, userList.Items.ToList());
            }
    复制代码

    上下文对象

    类 Hub 包含一个 Context 属性,该属性包含以下属性以及有关连接的信息:

    属性说明
    ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接都有一个连接 ID。
    UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。
    User 获取与当前用户关联的 ClaimsPrincipal。
    Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。
    Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
    ConnectionAborted 获取一个 CancellationToken,它会在连接中止时发出通知。

    Hub.Context 还包含以下方法:

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

    客户端对象

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

    属性说明
    All 对所有连接的客户端调用方法
    Caller 对调用了中心方法的客户端调用方法
    Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

    Hub.Clients 还包含以下方法:

    方法说明
    AllExcept 对所有连接的客户端调用方法(指定连接除外)
    Client 对连接的一个特定客户端调用方法
    Clients 对连接的多个特定客户端调用方法
    Group 对指定组中的所有连接调用方法
    GroupExcept 对指定组中的所有连接调用方法(指定连接除外)
    Groups 对多个连接组调用方法
    OthersInGroup 对一个连接组调用方法(不包括调用了中心方法的客户端)
    User 对与一个特定用户关联的所有连接调用方法
    Users 对与多个指定用户关联的所有连接调用方法

    这样我们Hub里面定义的方法,就可以利用这些对象来处理了。

    复制代码
            /// 
            /// 前端调用发送方法(发送信息给所有人)
            /// 
            /// 
            /// 
            public async Task ClientsSendMessagetoAll(MessageInput message)
            {
                await _chatHubContext.Clients.All.ReceiveMessage(message);
            }
    
            /// 
            /// 前端调用发送方法(发送消息给除了发送人的其他人)
            /// 
            /// 
            /// 
            public async Task ClientsSendMessagetoOther(MessageInput message)
            {
                var onlineuserlist = RedisHelper.Get>(CacheConst.KeyOnlineUser);
    
                var user = onlineuserlist.Where(x => x.UserId == message.UserId).ToList();
                if (user != null)
                {
                    await _chatHubContext.Clients.AllExcept(user[0].ConnectionId).ReceiveMessage(message);
                }
            }
    复制代码

    基于IHubContext的接口,我们也可以定义一个常规的接口函数,用于在各个服务类中调用Hub处理函数

        /// 
        /// 封装的SignalR的常规处理实现
        /// 
        public class HubContextService : BaseService, IHubContextService

    这样在服务端,注册服务后,可以使用这个自定义服务类的处理逻辑。

    //使用HubContextService服务接口
    builder.Services.AddSingletonHubContextService>();

    可以供一些特殊的控制器来使用Hub服务接口,如登录后台的时候,实现强制多端下线的处理方式。

    复制代码
        /// 
        /// 登录获取令牌授权的处理
        /// 
        [Route("api/[controller]")]
        [ApiController]
        public class LoginController : ControllerBase
        {
            private readonly IHubContextService _hubContextService;
    复制代码
    复制代码
            /// 
            /// 登录授权处理
            /// 
            /// 
            [AllowAnonymous]
            [HttpPost]
            [Route("authenticate")]
            public async Task Authenticate(LoginDto dto)
            {
                var authResult = new AuthenticateResultDto();
                ................
    
                var loginResult = await this._userService.VerifyUser(dto.LoginName, dto.Password, ip);
                if (loginResult != null && loginResult.UserInfo != null)
                {
                    var userInfo = loginResult.UserInfo;
    
                    ...............
    
                    //单用户登录
                    await this._hubContextService.SignleLogin(userInfo.Id.ToString());
                }
                else
                {
                    authResult.Error = loginResult?.ErrorMessage;
                }
    
    
                return authResult;
            }
    复制代码

     

    2、SignalR客户端

    .net客户端在对接Hub中心服务端的时候,需要添加Microsoft.AspNetCore.SignalR.Client的引用。

    Install-Package Microsoft.AspNetCore.SignalR.Client

    若要建立连接,请创建 HubConnectionBuilder 并调用 Build。 在建立连接期间,可以配置中心 URL、协议、传输类型、日志级别、标头和其他选项。 可通过将任何 HubConnectionBuilder 方法插入 Build 中来配置任何必需选项。 使用 StartAsync 启动连接。

    复制代码
    using System;
    using System.Threading.Tasks;
    using System.Windows;
    using Microsoft.AspNetCore.SignalR.Client;
    
    namespace SignalRChatClient
    {
        public partial class MainWindow : Window
        {
            HubConnection connection;
            public MainWindow()
            {
                InitializeComponent();
    
                connection = new HubConnectionBuilder()
                    .WithUrl("http://localhost:53353/ChatHub")
                    .Build();
    
                connection.Closed += async (error) =>
                {
                    await Task.Delay(new Random().Next(0,5) * 1000);
                    await connection.StartAsync();
                };
            }
    
            private async void connectButton_Click(object sender, RoutedEventArgs e)
            {
                connection.On<string, string>("ReceiveMessage", (user, message) =>
                {
                    this.Dispatcher.Invoke(() =>
                    {
                       var newMessage = $"{user}: {message}";
                       messagesList.Items.Add(newMessage);
                    });
                });
    
                try
                {
                    await connection.StartAsync();
                    messagesList.Items.Add("Connection started");
                    connectButton.IsEnabled = false;
                    sendButton.IsEnabled = true;
                }
                catch (Exception ex)
                {
                    messagesList.Items.Add(ex.Message);
                }
            }
    
            private async void sendButton_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    await connection.InvokeAsync("SendMessage", 
                        userTextBox.Text, messageTextBox.Text);
                }
                catch (Exception ex)
                {                
                    messagesList.Items.Add(ex.Message);                
                }
            }
        }
    }
    复制代码

    可以将 HubConnection 配置为对 HubConnectionBuilder 使用 WithAutomaticReconnect 方法来自动重新连接。 默认情况下,它不会自动重新连接。

    HubConnection connection= new HubConnectionBuilder()
        .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
        .WithAutomaticReconnect()
        .Build();

    在没有任何参数的情况下,WithAutomaticReconnect() 将客户端配置为在每次尝试重新连接之前分别等待 0、2、10 和 30 秒,在四次尝试失败后停止。

    为了测试Winform客户端对服务端的连接,我们可以新建一个小案例Demo,来测试信息处理的效果。

    创建一个测试的窗体如下所示(实际测试效果)。

     创建连接Hub中心的代码如下所示。

    复制代码
            /// 
            /// 初始化服务连接
            /// 
            private async Task InitHub()
            {
    ........
                //创建连接对象,并实现相关事件
                var url = serverUrl + $"/hubs/onlineUser?access_token={authenticateResultDto.AccessToken}";
                hubConnection = new HubConnectionBuilder()
                   .WithUrl(url)
                   .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) }) //自动连接
                   .Build();
    
                //接收实时信息
                hubConnection.On("ReceiveMessage", ReceiveMessage);
                //连接上处理在线用户
                hubConnection.On("OnlineUserList", OnlineUserList);
                //客户端收到服务关闭消息
                hubConnection.On("ForceOffline", async (ForceOfflineInput data) =>
                {
                    await CloseHub();
                });
    
                try
                {
                    //开始连接
                    await hubConnection.StartAsync();
    
                    var content = $"连接到服务器:{serverUrl}";
                    AddSystemMessage(content);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace);
    
                    var content = $"服务器连接失败:{ex.Message}";
                    AddSystemMessage(content);
    
                    InitControlStatus(false);
                    return;
                }
            }
    复制代码

    我们可以看到,客户端接收服务端的消息处理,通过下面代码进行处理。

    复制代码
    //接收实时信息
    hubConnection.On<MessageInput>("ReceiveMessage", ReceiveMessage);
    //连接上处理在线用户
    hubConnection.On<OnlineUserList>("OnlineUserList", OnlineUserList);
    //客户端收到服务关闭消息
    hubConnection.On("ForceOffline", async (ForceOfflineInput data) =>
    复制代码

    对于消息的接收处理,我们把它收到一个本地的集合列表中,然后统一处理即可。

    复制代码
    /// 
    /// 消息处理
    /// 
    /// JSON字符串
    private void ReceiveMessage(MessageInput data)
    {
        if (this.onlineUser != null)
        {
            var info = new MessageInfo(data);
             .............
            TryAddMessage(ownerId, info);
            BindTree();
        }
    }
    复制代码

    发送消息的时候,我们根据指向不同的用户,构造对应的消息体发送(调用服务端Hub接口)即可,调用通过InvokeAsync处理,接收相应的对象。

    复制代码
    private async void BtnSendMessage_Click(object sender, EventArgs e)
    {
        if (txtMessage.Text.Length == 0)
            return;
        var message = new MessageInput()
        {
            Title = "消息",
            Message = txtMessage.Text,
            MessageType = MessageTypeEnum.Info,
            UserId = this.toId,
            UserIds = new List<string>()
        };
    
        //判断发送人,是单个发送,还是广播发送所有人
        var methodName = !string.IsNullOrEmpty(this.toId) ? "ClientsSendMessage" : "ClientsSendMessagetoAll";
        await hubConnection.InvokeAsync(methodName, message);
    }
    复制代码

    测试功能正常,我们就可以把窗体整合到Winform端的主体界面中了。

    在Winform端的登陆处理的时候,我们把SignarR的主要处理逻辑放在全局类GlobalControl 中,方便调用,并定义好几个常用的对象,如连接,在线用户信息,消息列表等。

    并通过定义事件的方式,在消息变化的时候,通知界面进行更新处理。

    public event EventHandler SignalRMessageChanged;

    因此我们可以在主界面上提供一个入口,供消息的处理操作。

    主窗体在界面初始化的时候,调用一下全局类的初始化SignalR的Hub连接即可。

    复制代码
            /// 
            /// 初始化SignalR的处理
            /// 
            private async void InitSignalR()
            {
               await Portal.gc.InitHub();
            }
    复制代码

    这样就会根据相应的信息,实现HubConnection的初始化操作了,而且这个连接的生命周期是伴随整个应用的出现而出现的。

    打开就可以展示在线用户,并可以和系统相关用户发送实时消息了。如果可以,我们也可以把消息存储在数据库端,然后离线也可以收到存储起来,供下次登录后进行查看。

    窗体可以对SignalR消息进行实时的更新相应,通过事件的实现。

    复制代码
        public partial class FrmSignalClient : BaseDock
        {
            public FrmSignalClient()
            {
                InitializeComponent();
    
                Portal.gc.SignalRMessageChanged += SignalRMessageChanged;
            }
    复制代码

     由于篇幅的原因,后面在介绍在Vue3+Element的BS端中实现对SignalR消息整合的处理操作。

     

    SqlSugar开发框架介绍:https://www.iqidi.com/Framework/sugarIndex.htm 

    系列文章:

    《基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用》

    《基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理》

    《基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发》

    《基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理 》

    《基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转》

    《基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口 》

    《基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传》

     《基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录》

    《基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制》

    《基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理》

    《基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结》

    《基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理》

    《基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用》

    《基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用》

     《基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成》

    《基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍》

    《基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理》

     《基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面》

    《基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍》

    《基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理》

    《基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换》

     《基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理 》

    《基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求》

    《基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化 》

     基于SqlSugar的开发框架循序渐进介绍(25)-- 基于SignalR实现多端的消息通讯

    基于SqlSugar的开发框架循序渐进介绍(26)-- 实现本地上传、FTP上传、阿里云OSS上传三者合一处理

     

  • 相关阅读:
    FusionCharts Suite XT
    ssm毕设项目学生信息管理系统ow05a(java+VUE+Mybatis+Maven+Mysql+sprnig)
    聊聊「低代码」的实践之路
    一个关于CSDN资源定价的问题。
    vulnhub之GeminiInc
    用Go+绘制爱心给心爱的她表白
    一个或多个筛选器启动失败。完整的详细信息将在相应的容器日志文件中找到
    mysql 学习笔记-SQL执行加载顺序
    2023年“绿盟杯”四川省大学生信息安全技术大赛
    Flutter学习笔记 --事件处理
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/17296926.html
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号