一、概述
由于项目需要,最近公司项目里有个模块功能,需要使用到即时获得审批通知;原本的设计方案是使用ajax对服务器进行定时轮询查询,刚刚开始数据量和使用量不大的时候还好,后来使用量的增加和系统中各种业务的复杂度增加,服务器的压力也越来越大,于是我想使用消息推送的方式替换掉ajax轮询查询,当有审批提交时,调用推送方法,将消息推送到下一审批人那,这样就减低了服务器的压力。
Signal 是微软支持的一个运行在.NET平台上的 html websocket 框架。它出现的主要目的是实现服务器主动推送消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息。而且SignalR的兼容性也是很强大的,这里不在多言。既然选择了SignalR,那么就开始干吧!
我的想法是将SignalR做成一个自托管的服务,和我们的b/s项目分离出来,这样的好处是,1、推送服务不依赖于iis,就算iis挂了,我们的推送服务还可以正常运行;2、我们可以多平台调用这个推送服务,多个项目都可以同时使用;
二、创建服务端
废话不多说了,我也是第一次写博客,介绍完业务场景和构思,我们就开始撸码吧。
1、用VS创建一个名为 "SignalRProject" 的解决方案;
2、在 SignalRProject解决方案下新建一个名为Server的控制台
3、在程序包管理器控制台,输入如下命令
1 Install-Package Microsoft.AspNet.SignalR.SelfHost
4、输入如下命令:
1 Install-Package Microsoft.Owin.Cors
5、在Server控制台中添加UserInfo类,代码如下
1 using System; 2 3 namespace Server 4 { 5 public class UserInfo 6 { 7 public string ConnectionId { get; set; } 8 public string UserName { get; set; } 9 public DateTime LastLoginTime { get; set; } 10 } 11 }
6、在Server控制台中添加ChatHub类,代码如下
1 using Microsoft.AspNet.SignalR; 2 using Microsoft.AspNet.SignalR.Hubs; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Threading.Tasks; 7 8 namespace Server 9 { 10 [HubName("IMHub")] 11 public class ChatHub : Hub 12 { 13 // 静态属性 14 public static List<UserInfo> OnlineUsers = new List<UserInfo>(); // 在线用户列表 15 16 /// <summary> 17 /// 登录连线 18 /// </summary> 19 /// <param name="userId">用户Id</param> 20 /// <param name="userName">用户名</param> 21 public void Register(string userName) 22 { 23 var connnectId = Context.ConnectionId; 24 25 if (OnlineUsers.Count(x => x.ConnectionId == connnectId) == 0) 26 { 27 if (OnlineUsers.Any(x => x.UserName == userName)) 28 { 29 var items = OnlineUsers.Where(x => x.UserName == userName).ToList(); 30 foreach (var item in items) 31 { 32 Clients.AllExcept(connnectId).onUserDisconnected(item.ConnectionId, item.UserName); 33 } 34 OnlineUsers.RemoveAll(x => x.UserName == userName); 35 } 36 37 //添加在线人员 38 OnlineUsers.Add(new UserInfo 39 { 40 ConnectionId = connnectId, 41 UserName = userName, 42 LastLoginTime = DateTime.Now 43 }); 44 } 45 46 // 所有客户端同步在线用户 47 Clients.All.onConnected(connnectId, userName, OnlineUsers); 48 } 49 50 /// <summary> 51 /// 发送私聊 52 /// </summary> 53 /// <param name="toUserId">接收方用户连接ID</param> 54 /// <param name="message">内容</param> 55 public void SendPrivateMessage(string toUserName, string message) 56 { 57 var fromConnectionId = Context.ConnectionId; 58 59 var toUser = OnlineUsers.FirstOrDefault(x => x.UserName == toUserName); 60 var fromUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == fromConnectionId); 61 62 if (toUser != null ) 63 { 64 Clients.Client(toUser.ConnectionId).receivePrivateMessage(fromUser.UserName, message); 65 Clients.Client(toUser.ConnectionId).receivePrivateMessage(message); 66 } 67 else 68 { 69 //表示对方不在线 70 Clients.Caller.absentSubscriber(); 71 } 72 } 73 74 public void Send(string name, string message) 75 { 76 //Clients.All { get; } // 代表所有客户端 77 //Clients.AllExcept(params string[] excludeConnectionIds); // 除了参数中的所有客户端 78 //Clients.Client(string connectionId); // 特定的客户端,这个方法也就是我们实现端对端聊天的关键 79 //Clients.Clients(IList<string> connectionIds); // 参数中的客户端 80 //Clients.Group(string groupName, params string[] excludeConnectionIds); // 指定客户端组,这个也是实现群聊的关键所在 81 //Clients.Groups(IList<string> groupNames, params string[] excludeConnectionIds);参数中的客户端组 82 //Clients.User(string userId); // 特定的用户 83 //Clients.Users(IList<string> userIds); // 参数中的用户 84 85 Console.WriteLine("ConnectionId:{0}, InvokeMethod:{1}", Context.ConnectionId, "Send"); 86 Clients.All.addMessage(name, message); 87 } 88 89 /// <summary> 90 /// 连线时调用 91 /// </summary> 92 /// <returns></returns> 93 public override Task OnConnected() 94 { 95 Console.WriteLine("客户端连接,连接ID是:{0},当前在线人数为{1}", Context.ConnectionId, OnlineUsers.Count+1); 96 return base.OnConnected(); 97 } 98 99 100 /// <summary> 101 /// 断线时调用 102 /// </summary> 103 /// <param name="stopCalled"></param> 104 /// <returns></returns> 105 public override Task OnDisconnected(bool stopCalled) 106 { 107 var user = OnlineUsers.FirstOrDefault(u => u.ConnectionId == Context.ConnectionId); 108 109 // 判断用户是否存在,存在则删除 110 if (user == null) 111 { 112 return base.OnDisconnected(stopCalled); 113 } 114 115 Clients.All.onUserDisconnected(user.ConnectionId, user.UserName); //调用客户端用户离线通知 116 // 删除用户 117 OnlineUsers.Remove(user); 118 Console.WriteLine("客户端断线,连接ID是:{0},当前在线人数为{1}", Context.ConnectionId, OnlineUsers.Count); 119 return base.OnDisconnected(stopCalled); 120 } 121 122 public override Task OnReconnected() 123 { 124 return base.OnReconnected(); 125 } 126 } 127 }
7、在Server控制台中添加Startup类,代码如下
1 using Microsoft.Owin.Cors; 2 using Owin; 3 4 namespace Server 5 { 6 public class Startup 7 { 8 public void Configuration(IAppBuilder app) 9 { 10 //允许CORS跨域 11 app.UseCors(CorsOptions.AllowAll); 12 app.MapSignalR(); 13 } 14 } 15 }
8、修改Server控制台中添加Program类,代码如下
1 using Microsoft.Owin.Hosting; 2 using System; 3 4 namespace Server 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 string url = "http://localhost:10086";//设定 SignalR Hub Server 对外的接口 11 using (WebApp.Start(url))//启动 SignalR Hub Server 12 { 13 Console.WriteLine("Server running on {0}", url); 14 Console.ReadLine(); 15 } 16 } 17 } 18 }
9、F5运行起来
然后浏览器中访问http://localhost:10086/signalr/hubs
结果如下:
见上图内容就基本完成了,今天先讲到着,时间不早了,先休息了,后续有时间再将后面的文章补上