上一遍文章我讲了下自己的IdentityServer4整合到自己的项目里面,最近有改造了一下自己的原有的消息通知的功能,这里我用的是.net自带的组件Signalr,这是一款很不错的Socket组件,用起来非常简单,我这里讲一下自己的设计思路。,所以也把这个分享出来,如果大佬有觉得不妥的地方,欢迎指出。
首先是添加项目对Signalr的支持
services.AddSignalR();
Signalr提供了一个抽象类Hub,这是一个很重要的抽象类,我们要想使用Signalr,就必须实现它。我结合自己的需求,因为需要指点的客户发送,每个用户登录之后都会连接Singalr产生一个connectionid,准确的说是每个页面都会产生一个connectionid,所以难免会有一个用户打开多个相同的页面,这样就要给每个页面都发送消息通知。所以我的做法是将用户账号和connectionid存入内存中,connectionId作为key:
/// <summary> /// 连接对象集合 /// </summary> public static ConcurrentDictionary<string, string> ConnectionMaps = new ConcurrentDictionary<string, string>(); /// <summary> /// 添加连接对象 /// </summary> /// <param name="connectionId"></param> /// <param name="value"></param> public static void SetConnectionMaps(string connectionId, string value) { ConnectionMaps.AddOrUpdate(connectionId, value, (string s, string y) => value); }
添加一个类继承自Hub,Context是Hub一个上下文属性,SetConnectionMaps是自己定义的一个方法,以至于客户端可以调用这个方法,当你刷新或关闭页面时就会断开连接调用OnDisconnectedAsync,刷新时会产生新的connectionid:
public class SingalrClient : Hub { public void SetConnectionMaps(string account) { string connectionid = Context.ConnectionId; SingalrConnection.SetConnectionMaps(connectionid, account); } public override Task OnDisconnectedAsync(Exception exception) { SingalrConnection.Remove(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } }
然后再设置路由信息,/SingalrClient代表着路由匹配的地址:
app.UseEndpoints(endpoints => { endpoints.MapHub<SingalrClient>("/SingalrClient"); });
以上是完成了服务端的代码,接下是客户端,客户端需要添加引用 signalr.js,下载方法自行百度。引入好之后,根据自己的需求,在指定的页面来设置连接,我这里是登录之后调用SetConnectionMaps来存储账号与connectionid的内容:
var connection = new signalR.HubConnectionBuilder().withUrl("http://127.0.0.1:5004/SingalrClient").build(); $.ajax({ url: api + '/user/loginuser', type: 'get', success: function (response) { connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account).catch(function(errer){ console.error(errer.toString()) }); }); } } });
这个时候我们运行程序,就会看到如下的代表初步已经完成:
接下来我们来看如何向客户端发送消息,我来封装一个发送消息的类和它的接口,并且通过注入IHubContext,当然,你也可以直接通过Hub来直接发送,可以看到,我在每一个SendAsync方法里面都要一个字符串,这个字符串很重要,客户端就是根据这个字符串来接收的方法:
public interface ISingalrContent { /// <summary> /// 向所有客户端(用户)发送消息 /// </summary> /// <param name="message"></param> /// <returns></returns> Task SendAllClientsMessage(Message message); /// <summary> /// 向指定的部分客户端(用户)发送消息 /// </summary> /// <param name="connectionIds"></param> /// <param name="message"></param> /// <returns></returns> Task SendSomeClientsMessage(IReadOnlyList<string> connectionIds, Message message); /// <summary> /// 向指定的客户端(用户)发送消息 /// </summary> /// <param name="connectionIds"></param> /// <param name="message"></param> /// <returns></returns> Task SendClientMessage(Message message); }
public class SingalrContent : ISingalrContent { private IHubContext<SingalrClient> _hubContext; public SingalrContent(IHubContext<SingalrClient> hubContext) { _hubContext = hubContext; } #region 向客户端发送消息 public async Task SendAllClientsMessage(Message message) { await _hubContext.Clients.All.SendAsync("AllReviceMesage", message); } public async Task SendSomeClientsMessage(IReadOnlyList<string> connectionIds, Message message) { if (connectionIds == null || connectionIds.Count == 0) throw new ArgumentNullException("指定的客户端连接为空"); await _hubContext.Clients.Clients(connectionIds).SendAsync("SendClientMessage", message); } public async Task SendClientMessage(Message message) { if (string.IsNullOrEmpty(message.Revicer)) throw new ArgumentNullException("指定的客户端连接为空"); IReadOnlyList<string> connectionsByUser = SingalrConnection.GetConnectionIds(message.Revicer); await _hubContext.Clients.Clients(connectionsByUser).SendAsync("ReviceMesage", message); } #endregion }
services.AddScoped<ISingalrContent, SingalrContent>();
然后当又消息通过SingalrContent的发送时,客户端通过设置 connection.on的方法来确定接收的数据,就好比我这里,我需要根据登录的账号来接收指定的犯法,所以我传入ReviceMessage来接收,:
connection.on('ReviceMesage', function (message) { var count=$('#notice').html(); var oldNum = parseInt(count); var newNum = parseInt(message.data); $('#notice').html(newNum); if (oldNum < newNum) { $('#notice').addClass('blink'); } });
我还有个设计的是就是向所有的页面,不管有没有登录都发生消息,AllReviceMesage变对应的是SendAllClientsMessage(Message message)这个方法:
var connection = new signalR.HubConnectionBuilder().withUrl("http://111.229.211.248:5004/SingalrClient").build(); connection.on('AllReviceMesage',function(reviceMessage){ var data = { 'list': reviceMessage.data }; bindWhisper(data); } ); connection.start();
具体的调用就是这样子的:
public void SendWhisper(List<WhisperDTO> whisperDTOs) { Message message = new Message(); message.Data = whisperDTOs; _singalrContent.SendAllClientsMessage(message); } public void SendTidingsCount(string account, int count) { Message message = new Message(); message.Data = count; message.Revicer = account; _singalrContent.SendClientMessage(message); }
到此,我的设计思路已经讲完了,具体的代码可以参考:https://github.com/Hansdas/Blog_New/tree/master/Socket,我的项目的地址是:http://www.ttblog.site/