使用windows服务、SignalR实现双工通讯之服务端篇
最近工作需要客户端和服务端实现双工通讯,但是客户端不能是网页、窗体。想到了用wcf实现,但是wcf需要了解的东西较多,以前没有使用过,而且时间比较赶最终想到了SignalR。
科室SignalR网上的资料都是基于客户端是网页,几乎没有客户端服务端都为windows服务的博客和说明文档。通过自己一番尝试特将成果写出来,方便以后自己查阅和大家参考;
SignalR是微软提供的一个可以双工通讯的框架(我是这么认为),他最牛逼之处在于可以用最少的代码实现我们以前难以实现的服务端主动推送客户端。
其他的说明可以网上搜索一大堆了,不在此处再做说明。
本文主要介绍服务端和客户端均通过windows服务实现双工通讯;
1、首先新建一个类库,该类库用于实现SignalR服务端的封装;
2、使用Nuget下载所依赖的SignalR相关类库,下面的图是我Nuget上下载的库
感觉还是有蛮多库的,下了几个语言包。。。
3、新建一个SignalRService实现类,代码如下:
1 public class SignalRService : ThreadSafeLazyBaseSingleton<SignalRService> 2 { 3 4 /*SignalR服务端 5 所需:1、开放的端口号,读取xml实现 6 2、客户端实体(用于识别客户端),引用Model层 7 3、自定义SignalR.Hub通讯管道,重写开始连接、关闭连接事件 编写注册方法记录当前在线客户端 8 */ 9 private Logger log = new Logger(LogName.Signalr_Server.ToString()); 10 11 private IDisposable SignalR { get; set; } 12 //从配置文件读取路径和端口 13 private string ServerUrl { get 14 { 15 return "http://" + SignalRContext.Current.ServerIpAddress + ":" + SignalRContext.Current.ServerPort + "/signalr"; 16 } } 17 18 public List<ClientInfo> LstClients { get; internal set; } 19 20 public SignalRService() { 21 LstClients = new List<ClientInfo>(); 22 } 23 /// <summary> 24 /// 启动SignalR通讯服务 25 /// </summary> 26 public void StartServer() 27 { 28 try 29 { 30 log.Info("开始启动,url:" + ServerUrl); 31 SignalR = WebApp.Start<Startup>(ServerUrl); 32 log.Info("启动成功"); 33 } 34 catch (Exception ea) 35 { 36 log.Error("启动通讯服务失败!",ea); 37 throw new Exception("启动通讯服务失败!" + ea.Message); 38 } 39 } 40 41 public void Dispose() 42 { 43 if (SignalR != null) 44 { 45 SignalR.Dispose(); 46 } 47 } 48 49 class Startup 50 { 51 private Logger log = new Logger(LogName.Signalr_Server.ToString()); 52 public void Configuration(IAppBuilder app) 53 { 54 try 55 { 56 log.Info("进入Configuration"); 57 var hubConfiguration = new HubConfiguration 58 { 59 EnableDetailedErrors = true //允许抛出异常明细信息 60 }; 61 62 app.UseCors(CorsOptions.AllowAll); 63 log.Info("执行完UseCors"); 64 app.RunSignalR(hubConfiguration); 65 app.MapSignalR(); 66 log.Info("执行完MapSignalR"); 67 68 } 69 catch (Exception ea) 70 { 71 log.Error("启动通讯服务异常:" + ea.Message); 72 } 73 } 74 } 75 }
4、继承Hub类(官网称为集线器类),Hub类可以理解为SignalR服务端提供给客户端的通讯管道(我是这么理解的,欢迎大神纠正)。这个类有3个比较关键的事件:
OnConnected、 //有客户端连接时候触发
OnDisconnected、 //客户端释放资源时候触发
OnReconnected、 //客户端断开连接后又重新连接时触发
基于上面的事件实现服务端的相关逻辑。继承Hub类后,除了重写上面事件外,还可以新增方法。这种新增的方法可以理解为服务端提供给客户端远程调用的方法,如何调用在客户端说明时会写。下面是我继承的Hub类:
1 [HubName("CustomHub")] 2 public class CustomHub: Hub 3 { 4 private JsonSerializerSettings JsonSetting; 5 private Logger Log = new Logger(LogName.Signalr_Server.ToString()); 6 private DbUtility Db = new DbUtility(SignalRContext.Current.DbConn, DbProviderType.SqlServer); 7 #region 重写事件 8 //客户端通道连接时触发 9 public override Task OnConnected() 10 { 11 Log.Info("进入OnConnected方法"); 12 if (SignalRService.Current.LstClients == null) 13 SignalRService.Current.LstClients = new List<ClientInfo>(); 14 15 JsonSetting = new JsonSerializerSettings(); 16 JsonSetting.NullValueHandling = NullValueHandling.Ignore; 17 string queryString = Context.QueryString["ClientJson"].ToString(); 18 ClientInfo client = JsonConvert.DeserializeObject<ClientInfo>(queryString, JsonSetting); 19 Log.Info("接收客户端的client:" + queryString); 20 21 //获取到客户端传来的客户端信息 22 lock (SignalRService.Current.LstClients) 23 { 24 if (SignalRService.Current.LstClients.Exists(p => p.ConnId == Context.ConnectionId)) 25 {//如果服务端已存在此连接(理论上不存在),从现有集合移除再添加 26 Log.Info("开始调用客户端测试方法!"); 27 SignalRService.Current.LstClients.RemoveAll(p => p.ConnId == Context.ConnectionId); 28 Log.Info("客户端测试方法调用完毕!"); 29 } 30 else 31 {//从数据库得到客户端信息并添加到集合中 32 33 if (SignalRContext.Current.IsDebug) 34 {//如果为debug模式调用客户端的测试方法 35 Log.Info("服务端开始调用客户端方法TestClient"); 36 Clients.Client(Context.ConnectionId).TestClient(); 37 } 38 else 39 { 40 Dictionary<string, object> input = new Dictionary<string, object>(); 41 input.Add("clientid", client.ClientId); 42 List<ClientInfo> lst = ServerHandl.Current.GetClinetInfo(input); 43 if (lst == null || lst.Count == 0) 44 {//如果数据库没有此机器记录.. 45 Log.Error("数据库没有机器id[" + client.ClientId + "]的建档记录,客户端机器链接失败!"); 46 } 47 else 48 { 49 SignalRService.Current.LstClients.Add(lst[0]); 50 lst[0].ConnId = Context.ConnectionId; 51 lst[0].TransTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); 52 lst[0].TransFlag = 1; 53 //保存到数据库 54 SrServerHandl.Current.SaveLoginClientInfo(lst[0]); 55 //将数据库的记录返回给客户端 56 Clients.Client(Context.ConnectionId).PerfectClientAttribute(lst[0]); 57 } 58 } 59 } 60 } 61 62 return base.OnConnected(); 63 } 64 //客户端通道断开连接时触发 65 public override Task OnDisconnected(bool stopCalled) 66 { 67 return base.OnDisconnected(stopCalled); 68 } 69 //客户端离线后重新连接时触发 70 public override Task OnReconnected() 71 { 72 return base.OnReconnected(); 73 } 74 #endregion 75 76 //测试数据传输方法 77 public ClientInfo SendTest(string name) 78 { 79 Log.Info("客户端执行服务端方法,参数name:" + name); 80 Log.Info(" Context.ConnectionId:" + Context.ConnectionId); 81 ClientInfo info = new ClientInfo(); 82 info.ClientName = name; 83 info.ConnId = Context.ConnectionId; 84 return info; 85 } 86 87 }
5、新建一个服务端处理类,我是用来存储先关数据用
1 public class SrServerHandl: ThreadSafeLazyBaseSingleton<SrServerHandl> 2 { 3 private DbUtility Db = new DbUtility(SignalRContext.Current.DbConn, DbProviderType.SqlServer); 4 5 public SrServerHandl() 6 { 7 } 8 /// <summary> 9 /// 保存客户端登录信息 10 /// </summary> 11 /// <param name="client"></param> 12 public void SaveLoginClientInfo(ClientInfo client) 13 { 14 //保存到数据库 15 List<ParameterEx> ps = new List<ParameterEx>(); 16 ps.Add(DbUtility.NewSqlStringParameters("connid", client.ConnId, Type.GetType("System.String"))); 17 ps.Add(DbUtility.NewSqlStringParameters("transtime", DateTime.Now, Type.GetType("System.DateTime"))); 18 ps.Add(DbUtility.NewSqlStringParameters("transflag", 1, Type.GetType("System.Int32"))); 19 ParameterEx keyParme = DbUtility.NewSqlStringParameters("clientid", client.ClientId, Type.GetType("System.String")); 20 string sql = DbUtility.CreateUpdateSqlString(SqlServerManager.Table_client_info, ps, keyParme); 21 Db.ExecuteNonQuery(sql, null); 22 } 23 }
6、新建windows服务,引用SignalR类库并调用相关方法
1 public partial class SignalRService : ServiceBase 2 { 3 public SignalRService() 4 { 5 InitializeComponent(); 6 } 7 8 protected override void OnStart(string[] args) 9 { 10 //开始启动服务 11 SignalRService.Current.StartServer(); 12 } 13 14 protected override void OnStop() 15 { 16 //释放服务 17 SignalRService.Current.Dispose(); 18 } 19 }