- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
基于ASP.NET Core SignalR 可以实现客户端和服务器之间进行即时通信。本篇随笔介绍一些SignalR的基础知识,以及结合对SqlSugar的开发框架的支持,实现SignalR的多端处理整合,从而实现Winform客户端,基于Vue3+ElementPlus的BS端整合,后面也可以实现对移动端的SignalR的整合通讯.
适合 SignalR 的应用场景:
SignalR 自动选择服务器和客户端能力范围内的最佳传输方法,如 WebSockets 、Server-Sent Events、长轮询。Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。SignalR 提供两个内置中心协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议.
客户端负责通过 HubConnection 对象建立到服务器终结点的连接。 Hub 连接在每个目标平台中表示:
当中心连接实例成功启动后,消息可以自由地双向流动。 用户可以自由地将通知发送到服务器,以及从服务器接收通知。 客户端是任何已连接的应用程序,例如(但不限于)Web 浏览器、移动应用或桌面应用.
在.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; } }
客户端在其 .On(...) 处理程序中返回结果,如下所示:
hubConnection.On( " GetMessage " , async () => { Console.WriteLine( " Enter message: " ); var message = await Console.In.ReadLineAsync(); return message; });
hubConnection.on(" GetMessage ", async () => { let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve( "message" ); }, 100 ); }); return promise; });
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一般还需要重写连接和断开的处理操作,如下代码所示.
如对于用户的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; }
。
.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中心的代码如下所示.
/// <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 中,方便调用,并定义好几个常用的对象,如连接,在线用户信息,消息列表等.
并通过定义事件的方式,在消息变化的时候,通知界面进行更新处理.
public event EventHandler<MessageInfo> SignalRMessageChanged;
因此我们可以在主界面上提供一个入口,供消息的处理操作.
主窗体在界面初始化的时候,调用一下全局类的初始化SignalR的Hub连接即可.
/// <summary> /// 初始化SignalR的处理 /// </summary> 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的开发框架循序渐进介绍(25)--基于SignalR实现多端的消息通讯的文章就讲到这里了,如果你想了解更多关于基于SqlSugar的开发框架循序渐进介绍(25)--基于SignalR实现多端的消息通讯的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
晚上在 QQ 上看到昵称为“乱码”的好友回答了搜搜问问里一个问题: 在VBS中有办法定义字节数组么? 在VBS中有办法定义字节数组么?就是字节子类型数组(VarType是8209的那种)注意不是V
例如,员工管理应用程序可能包括一个EmPloyee 类。然后可以用这个类来创建和维护特定实例,比如Gonn和Sally。 根据预定义的类创建对象常称为类的实例化(class insta
在自然语言中,我们理解抽象的概念是,一个物体的一种大的描述,这种描述对某类物体来说是共有的特性。那么在PHP中也是一样的,我们把一个类进行抽象,可以指明类的一般行为,这个类应该是一个模板,它指示它的
DBA_2PC_PENDING Oracle会自动处理分布事务,保证分布事务的一致性,所有站点全部提交或全部回滚。一般情况下,处理过程在很短的时间内完成,根本无法察觉到。但是,如果在commit或
目录 计算过程 投影分量计算 假设你有一家理发店,已经记录了过去一年中所有顾客的头发长度和发型偏好的数据。现在你想从这些数据中提取一些主要的信息,比如顾客最常
Object.defineProperty函数会直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象。 一、简单使用 const obj = {} Object.defineP
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
一 点睛 Thrift 是一歀基于 CS 架构的 RPC 框架,最初由 Facebook 研发,2008 年转入 Apache 组织。开发人员可以使用 Thrift 提供的 IDL(接口定义语言)来定
数据库应用程序与主应用程序分开存在,并存储数据集合。 每个数据库都使用一个或多个API来创建,访问,管理,搜索和复制其包含的数据。 数据库还使用非关系数据源,例如对象或文件。 然而,数据库证明是大数
介绍 Ant是一个 Apache 基金会下的跨平台的基于 Java 语言开发的构件工具。在我们详细了解 Apache Ant 之前, 让我们来讲解为什么构建工具是需要最先了解的。 构建工具的需求
我现在正在尝试学习ocaml,并希望从一个小程序开始,生成所有位组合: [“0”,“0”,“0”] [“0”,“0”,“1”] [“0”,“1”,“0”] ... 等等 我的想法是下面的代码: let
我正在做我的介绍 C 类(class)作业,我的任务是执行以下任务...... 为一个函数编写代码,该函数通过值接收两个参数(a 和 b)并通过引用具有另外两个参数(c 和 d)。所有参数都是双倍的。
我希望提供有关我网站内容的快速演示,以及如何在用户访问我的页面后立即以正确的方式使用它们。我希望使用顶部的弹出式窗口进行演示。 我的意思是小信息框,一个接一个地通知用户各个步骤。任何人都可以帮助我如何
与C、Java等语言一样,JavaScript中可以用&&、||、!三个逻辑判断符来对boolean值进行逻辑判断。与C、Java不同的是,JavaScript中逻辑与(&&
JavaScript中,==与===操作符均可用于判断两个值是否相等;不同之处在于,如果进行判断的两个值类型不一致,===操作符会直接返回false,而==操作符则会在类型转换后再进行判断。详细的判
JavaScript中,object转换为boolean的操作非常简单:所有的object转换成boolean后均为true;即使是new Boolean(false)这样的object在转换为bo
在android开发中,当不满足触发条件就按返回键的时候,就要对此进行检测。尤其是当前Activity需要往前一个Activity传送消息时。即Activity1跳转到Activity3如果采用的是
背景 当要求系统启动一个应用程序时,系统会先查找当前命令是否是内部命令,若不是,则在当前目录下查找,如果仍没有找到,则在系统变量 Path 指定的路径去查找。JDK(Java Developmen
概述 想做一个微信的公众平台,阅读了微信官方给的网址接入的示例代码,发现有个问题好像一直都是半知半解的,就是在类里边直接使用$_GET。仔细查了下关于这方面的知识,发现PHP中这部分的基础知识掌握
我是一名优秀的程序员,十分优秀!