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

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

时间:2023-09-16 15:05:45浏览次数:39  
标签:25 基于 Hub -- public SignalR message 连接 客户端

基于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 连接在每个目标平台中表示:

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

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

1、SignalR服务端

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

builder.Services.AddSignalR();// 即时通讯

app.UseEndpoints(endpoints =>
{
    // 注册集线器
    endpoints.MapHub<OnlineUserHub>("/hubs/onlineUser");
});

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

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

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

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

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

public class ChatHub : Hub<IChatClient>
{
    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<IChatClient> 可以对客户端方法进行编译时检查。 这可以防止使用字符串引起的问题,因为 Hub<T> 只能提供对 接口中定义的方法的访问权限。 使用强类型 Hub<T> 会禁止使用 SendAsync

Hub服务端中心

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    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<IOnlineUserHub>
{
        private readonly IOnlineUserService _onlineUserService;
        private readonly IHubContext<OnlineUserHub, OnlineUserHub> _chatHubContext;

        public OnlineUserHub(IOnlineUserService onlineUserService,
            IHubContext<OnlineUserHub, IOnlineUserHub> onlineUserHubContext)
        {
            _onlineUserService = onlineUserService;
            _chatHubContext = onlineUserHubContext;
        }
}

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

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

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

/// <summary>
        /// 连接后处理
        /// </summary>
        /// <returns></returns>
        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里面定义的方法,就可以利用这些对象来处理了。

/// <summary>
        /// 前端调用发送方法(发送信息给所有人)
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task ClientsSendMessagetoAll(MessageInput message)
        {
            await _chatHubContext.Clients.All.ReceiveMessage(message);
        }

        /// <summary>
        /// 前端调用发送方法(发送消息给除了发送人的其他人)
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task ClientsSendMessagetoOther(MessageInput message)
        {
            var onlineuserlist = RedisHelper.Get<List<OnlineUserInfo>>(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处理函数

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

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

//使用HubContextService服务接口
builder.Services.AddSingleton<IHubContextService, HubContextService>();

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

/// <summary>
    /// 登录获取令牌授权的处理
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        private readonly IHubContextService _hubContextService;
/// <summary>
        /// 登录授权处理
        /// </summary>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpPost]
        [Route("authenticate")]
        public async Task<AuthenticateResultDto> 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,来测试信息处理的效果。

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

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

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

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

/// <summary>
        /// 初始化服务连接
        /// </summary>
        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<MessageInput>("ReceiveMessage", ReceiveMessage);
            //连接上处理在线用户
            hubConnection.On<OnlineUserList>("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) =>

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

/// <summary>
/// 消息处理
/// </summary>
/// <param name="data">JSON字符串</param>
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 中,方便调用,并定义好几个常用的对象,如连接,在线用户信息,消息列表等。

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

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

public event EventHandler<MessageInfo> SignalRMessageChanged;

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

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

/// <summary>
        /// 初始化SignalR的处理
        /// </summary>
        private async void InitSignalR()
        {
           await Portal.gc.InitHub();
        }

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

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

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

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

public partial class FrmSignalClient : BaseDock
    {
        public FrmSignalClient()
        {
            InitializeComponent();

            Portal.gc.SignalRMessageChanged += SignalRMessageChanged;
        }

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

 

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

标签:25,基于,Hub,--,public,SignalR,message,连接,客户端
From: https://blog.51cto.com/wuhuacong/7493575

相关文章

  • 《安富莱嵌入式周报》第307期:开源智能制冷板,Keil MDK6发布时间,编程助手Github Copilot
     视频版:https://www.bilibili.com/video/BV1fV4y1X7sk 1、KeilMDK6最终定于2023年末发布https://www.keil.com/pr/article/1302.htmMDK6的发布消息最终尘埃落定,定于2023年末发布。相比现在的MDK,主要是集成了功能安全库及其编译器,KeilStudio桌面版,跨平台支持。2、开源智能冷却板......
  • springboot安卓音乐播放器
    开发环境及工具:大等于jdk1.8,大于mysql5.5,idea(eclipse),AndroidStudio技术说明:springbootmybatisandroid代码注释齐全,没有多余代码,适合学习(毕设),二次开发,包含论文技术相关文档。功能介绍:用户端:登录注册首页显示搜索音乐,轮播图,音乐列表点击音乐进入音乐详情(以及展示评论信息),可以点......
  • Qemu源码分析(3)—Apple的学习笔记
    一,前言本次主要分析object_new,也就是了解最关键的object类对象。二,源码分析看上去就是通过TypeImpl来创建Object。Object*object_new(constchar*typename){TypeImpl*ti=type_get_by_name(typename);returnobject_new_with_type(ti);}主要调用初始化object,把type中的......
  • 2023年找工作的心酸历程
    前几天在脉脉上看到一个热议话题“23年找工作的心酸历程”大家都知道近几年互联网大环境不好,找工作变得越来越卷了、就算是BAT这种大厂出来的,也不见得就有多好找工作,可想而知,如果你的背景和能力不是特别强,很有可能练简历关都过不了。特别是工作时间久的老程序员,总包可能较高,相比其......
  • IDEA神器插件-40款IDEA神器插件-40款
    IDEA神器插件-40款IDEA插件安装步骤IDEA里面,选择打开File-->Settings-->Plugins在Plugins里面,可以搜索需要的插件(下面的标题),然后安装如下图:AceJump全栈必备,作为一个全能的程序员,用鼠标,太伤自尊了,他就可以帮到你AceJump其实是一款能够代替鼠标的软件,只要安装了这款......
  • Maven 与 Gradle 的区别
    目录Maven与Gradle的区别前言1.Maven与Gradle对比2.构建流程和生命周期3.包管理和传递性依赖总结:深入了解gradle和maven的区别 Maven与Gradle的区别 刘文正_ 程序大视界 2020/04/2720:28  前言Java世界中主要有三大构建工具:Ant、Maven和Gradle。经过几年......
  • 【译】使用 ChatGPT 和 Azure Cosmos DB 构建智能应用程序
    原文|MarkBrown翻译|郑子铭随着对智能应用程序的需求不断增长,开发人员越来越多地转向人工智能(AI)和机器学习(ML),以增强其应用程序的功能。聊天机器人已经成为提供对话式人工智能的最流行方式之一。ChatGPT是OpenAI开发的大型语言模型(LLM),是构建能够理解自然语言并提供智能响应的聊......
  • JVM-CMS分享
    01.垃圾回收算法 垃圾回收算法-种类标记-清除(mark-sweep)标记-压缩(mark-compact)复制算法(copy)分代收集算法(GenerationalGC) 标记-清除(mark-sweep) 原理:     1.标记死亡的对象为空闲内存,并记录在一个空闲链表(free-list)之中。     2.清除确定不可用的对象。缺点: ......
  • maven 创建项目
    目录一:  配置Maven1:Maven是什么2、Maven安装--windows3:Maven配置文件二:Ecplise maven配置三:Eclipse maven 创建父子项目1、创建父级工程2、创建Maven子工程 --->  jar包子工程创建3.创建Maven子工程 ---> war包子工程创建一:  配置Maven1:Maven是什么......
  • centos搭建禅道
    下载禅道包:https://www.zentao.net/download/80137.html将包上传至linux中/opt目录下,可通过xftp,后解压该包,解压完成后,生成zbox文件夹3.修改端口,为了不占用服务器上默认的80和3306端口,需要修改禅道自带的apache、mysql端口/opt/zbox/zbox-ap8090-mp8091开放端口firewall-cmd-......