SignalR 的应用
一.应用场景: 在项目中有一个地方需要定时查询数据库是否有数据,如果有则显示在界面上。
二.可以使用ajax定时查询来做:
var inter = window.setInterval(refreshData,30000); //每三十秒调用一次
function refreshData() { $.ajax({ cache: false, type: "POST", url: "@(Url.Action("TJInfoData", "TJInfo"))", success: function (data) { if (data.code == "200") { //alert(data.msg.Name); //更新数据 } else { //alert(data.msg); //无数据时,界面要清数据 //基本信息 $("#Lsh").text(""); } }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError); } }); }
三.ajax定时查询虽然简单明了,但是听说了signalr 后决定试下。
1. 添加包,nuget 搜索SignaR 安装即可。
2. 在前端页面添加js:
<!--Reference the SignalR library. --> <script src="~/Scripts/jquery.signalR-2.2.2.js"></script> <!--Reference the autogenerated SignalR hub script. --> <script src="~/signalr/hubs"></script> <!--Reference the StockTicker script. --> <script src="~/Scripts/TJDataQueryScript.js"></script>
其中 TJDataQueryScript.js 为利用signaR 进行通信的方法。
3. 首先创建路由:
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(OfficialAgentWeb.SignalRHub.Startup))] namespace OfficialAgentWeb.SignalRHub { public class Startup { public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here app.MapSignalR(); } } }
再创建连接器:
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Data.Domain; using Data.Infrastructure; using Data.Log4Net; using Data.Services; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; namespace OfficialAgentWeb.SignalRHub { [HubName("TJDataHubMini")] public class TJDataHub : Hub { private readonly TJDataQuery _tjdata; public TJDataHub() : this(TJDataQuery.Instance) { } public TJDataHub(TJDataQuery stockTicker) { _tjdata = stockTicker; } public override Task OnConnected() { var AdminId = Context.QueryString["AdminId"]; var YewuIsSuper = Context.QueryString["YewuIsSuper"]; WriteLog.Info("OnConnected: ConnectionId=" + Context.ConnectionId + ",AdminId="+ AdminId + ",YewuIsSuper=" + YewuIsSuper); if (!_tjdata.ConnectIdAdminIdDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdAdminIdDic.Add(Context.ConnectionId, AdminId); } if (!_tjdata.ConnectIdIsSuperDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdIsSuperDic.Add(Context.ConnectionId, YewuIsSuper); } // int adminIdInt = 0; int.TryParse(AdminId, out adminIdInt); bool YewuIsSuperBool = YewuIsSuper.ToLower().Equals("true") ? true : false; var signalRConnectIdService = EngineContext.Current.Resolve<ISignalRConnectIdService>(); signalRConnectIdService.Insert(new M_SignalRConnectId { ConnectId = Context.ConnectionId, AdminId = adminIdInt, YewuIsSuper = YewuIsSuperBool, IsDeleted = false, IsActive = false }); return base.OnConnected(); } public override Task OnDisconnected(bool stopCalled) { var AdminId = Context.QueryString["AdminId"]; var YewuIsSuper = Context.QueryString["YewuIsSuper"]; WriteLog.Info("OnDisconnected: ConnectionId=" + Context.ConnectionId + ",AdminId="+ AdminId + ", YewuIsSuper=" + YewuIsSuper); if (_tjdata.ConnectIdAdminIdDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdAdminIdDic.Remove(Context.ConnectionId); } if (_tjdata.ConnectIdIsSuperDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdIsSuperDic.Remove(Context.ConnectionId); } // int adminIdInt = 0; int.TryParse(AdminId, out adminIdInt); var signalRConnectIdService = EngineContext.Current.Resolve<ISignalRConnectIdService>(); var cidQ = signalRConnectIdService.GetByConnectId(Context.ConnectionId); if (cidQ != null) { var cidL = cidQ.ToList(); if (!(cidL == null || cidL.Count<1)) { cidL.ForEach(p => p.IsDeleted = true); signalRConnectIdService.Update(cidL); } } return base.OnDisconnected(stopCalled); } public override Task OnReconnected() { var AdminId = Context.QueryString["AdminId"]; var YewuIsSuper = Context.QueryString["YewuIsSuper"]; WriteLog.Info("OnReconnected: ConnectionId=" + Context.ConnectionId + ",AdminId=" + AdminId + ",YewuIsSuper=" + YewuIsSuper); if (!_tjdata.ConnectIdAdminIdDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdAdminIdDic.Add(Context.ConnectionId, AdminId); } if (!_tjdata.ConnectIdIsSuperDic.ContainsKey(Context.ConnectionId)) { _tjdata.ConnectIdIsSuperDic.Add(Context.ConnectionId, YewuIsSuper); } // int adminIdInt = 0; int.TryParse(AdminId, out adminIdInt); bool YewuIsSuperBool = YewuIsSuper.ToLower().Equals("true") ? true : false; var signalRConnectIdService = EngineContext.Current.Resolve<ISignalRConnectIdService>(); signalRConnectIdService.Insert(new M_SignalRConnectId { ConnectId = Context.ConnectionId, AdminId = adminIdInt, YewuIsSuper = YewuIsSuperBool, IsDeleted = false, IsActive = false }); return base.OnReconnected(); } public IEnumerable<TJDataForTimer> GetAllStocks() { return null; } [HubMethodName("NewContosoChatMessage")] public void NewContosoChatMessage(string cid) { WriteLog.Info("TJDataQuery--IsAlive--cid=" + cid); } } }
创建一个单例来定时发布数据 给客户端:
using Bll.Admin; using Data.Domain; using Data.Infrastructure; using Data.Log4Net; using Data.Services; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using OfficialAgentWeb.Controllers; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; using System.Web.SessionState; namespace OfficialAgentWeb.SignalRHub { public class TJDataQuery { // Singleton instance private readonly static Lazy<TJDataQuery> _instance = new Lazy<TJDataQuery>(() => new TJDataQuery(GlobalHost.ConnectionManager.GetHubContext<TJDataHub>().Clients)); private readonly ConcurrentDictionary<string, TJDataForTimer> _stocks = new ConcurrentDictionary<string, TJDataForTimer>(); private readonly object _updateStockPricesLock = new object(); //stock can go up or down by a percentage of this factor on each change private readonly double _rangePercent = .002; private readonly TimeSpan _updateInterval = TimeSpan.FromSeconds(30); //TimeSpan.FromMilliseconds(250); private readonly Random _updateOrNotRandom = new Random(); //private readonly Timer _timer; public Timer _timer { get; set; } private volatile bool _updatingStockPrices = false; private TJDataQuery(IHubConnectionContext<dynamic> clients) { Clients = clients; ConnectIdAdminIdDic = new Dictionary<string, string>(); ConnectIdIsSuperDic = new Dictionary<string, string>(); _stocks.Clear(); _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval); } public static TJDataQuery Instance { get { return _instance.Value; } } private IHubConnectionContext<dynamic> Clients { get; set; } //ConnectId 和 AdminId 的字典 public Dictionary<string, string> ConnectIdAdminIdDic { get; set; } //ConnectId 和 IsSuperDic 的字典 public Dictionary<string, string> ConnectIdIsSuperDic { get; set; } public IEnumerable<TJDataForTimer> GetAllStocks() { //return _stocks.Values; return null; } private void UpdateStockPrices(object state) { lock (_updateStockPricesLock) { if (!_updatingStockPrices) { _updatingStockPrices = true; //from database var signalRConnectIdService = EngineContext.Current.Resolve<ISignalRConnectIdService>(); var cidQ = signalRConnectIdService.GetAllConnect(); if (cidQ != null) { var cidL = cidQ.ToList(); if (!(cidL == null || cidL.Count < 1)) { //根据adminid查数据,然后分发推送到客户端。 Dictionary<int, List<string>> adminIdDic = new Dictionary<int, List<string>>(); foreach (var p in cidL) { if (!adminIdDic.ContainsKey(p.AdminId)) adminIdDic.Add(p.AdminId, new List<string> { p.ConnectId }); else adminIdDic[p.AdminId].Add(p.ConnectId); } // foreach (var a in adminIdDic) { var ayewu = false; var cidL1 = cidL.Where(x => x.AdminId == a.Key).ToList(); if (!(cidL1 == null || cidL1.Count < 1)) { ayewu = cidL1[0].YewuIsSuper; } var m = GetTJDataQuery(a.Key, ayewu); //分发 foreach (var connectid in a.Value) { WriteLog.Info("BroadcastStockPrice begin: AdminId=" + a.Key + ",connectid="+ connectid); BroadcastStockPrice(m, connectid); } } } } // Clients.All.IsAlive(); //foreach (var a in ConnectIdAdminIdDic) //{ // var m = GetTJDataQuery(a.Value, a.Key); // WriteLog.Info("BroadcastStockPrice begin: AdminId=" + a.Value); // BroadcastStockPrice(m, a.Key); //} _updatingStockPrices = false; } } } private void BroadcastStockPrice(TJDataForTimer stock, string connectId) { if (stock == null || (!stock.code.Equals("200"))) { WriteLog.Info("updateStockPrice begin: connectId=" + connectId + ", 推送空的内容"); } else { WriteLog.Info("updateStockPrice begin: connectId=" + connectId + ", 推送了:" + (stock.msg == null ? "" : stock.msg.Name)); } Clients.Client(connectId).updateStockPrice(stock); //Clients.All.updateStockPrice(stock); } private TJDataForTimer GetTJDataQuery(int AdminId, bool YewuIsSuperBool) { TJDataForTimer value = new TJDataForTimer(); B_TJInfo b = new B_TJInfo(); //int intAdmin = 0; //int.TryParse(AdminId, out intAdmin); //审核数量,待审核:0 今天已审核:0 今天审核通过:0 今天审核不通过:0 if (AdminId<1) { WriteLog.Info("GetTJDataQuery AdminId <0: AdminId=" + AdminId); return null; } count_sh count_Sh = b.GetCountSh(AdminId, YewuIsSuperBool); if (count_Sh == null) { } // M_TJInfo m = b.GetNewSingle(AdminId, YewuIsSuperBool); if (m == null) { WriteLog.Info("GetTJDataQuery GetNewSingle is null"); //return null; value.code = "401"; value.msgCountsh = count_Sh; return value; } //TJInfoDetail List<M_TJInfoDetail> detailList = b.GetDetail(m.Id); if (detailList == null || detailList.Count < 1) { } //MachineCheck List<M_MachineCheck> machineList = b.GetMachine(m.OperateNo); if (machineList == null || machineList.Count < 1) { } value.code = "200"; value.msg = m; value.msgDetail = detailList; value.msgMachine = machineList[0]; value.msgCountsh = count_Sh; return value; //return Json(new { code = "200", msg = m, msgDetail = detailList, msgMachine = machineList[0], msgCountsh = count_Sh }); } } }
4. 运行时碰到的问题:
A. 客户端可以传递参数给服务器:
$.connection.hub.qs = { 'AdminId': $("#AdminIdHid").val(), 'YewuIsSuper': $("#YewuIsSuperHid").val()};
在服务端比如在OnConnected()里 使用 var AdminId = Context.QueryString["AdminId"]; 获取值。
B. 每次连接都会调用OnConnected()方法,失去连接会调用OnDisconnected 方法,由于超时等原因暂时连不上,然后又连上的调用OnReconnected重连,但是重连后connectId会变。
在OnConnected() 方法里把connectId 保存到数据库里。
在OnDisconnected 方法里 更新该connectId为失去连接。
但是在测试过程中,反复刷新该页面,也有可能出现某次OnDisconnected 没有调用的情况。
所有就会出现数据库里发现有3个connectId 在连接, 但是其实可能只有1个客户端在连接。
暂时想到的解决方法是:
服务端启动一个定时器每30分钟调用一次,调用客户端的方法,而客户端的方法是传递connectId到服务端的另外一个方法里。在服务器的该方法里可以根据connectId设置该客户端为活动状态。
服务器端创建一个任务Task,定时跑,查询连接表,如果发现该connectId 不是活动状态,则更新此数据。
代码:
Clients.All.IsAlive();
服务端调用客户端的IsAlive 方法。
然后客户端:
var ticker = $.connection.TJDataHubMini; ticker.client.IsAlive = function () { var clinetConectId = $.connection.hub.id+''; console.log('clinetConectId=' + clinetConectId); ticker.server.NewContosoChatMessage(clinetConectId).done(function () { console.log('IsAliveService done'); }); }
客户端得到 ConectId 后调用服务端的 NewContosoChatMessage 的方法。
[HubMethodName("NewContosoChatMessage")] public void NewContosoChatMessage(string cid) { WriteLog.Info("TJDataQuery--IsAlive--cid=" + cid); }
可以在 此 NewContosoChatMessage 方法里更新数据表。
服务端创建任务参见另外一篇文章。