前言:最近手上一个项目需要后端实时推送数据到前端,第一个想到的就是微软的SignalR,由于之前都是平时没事写的Demo,没有用到实际项目中,这次恰好用到了,因此记录下来整个实现过程(网上也有很多类似的教程,写的不好,请指正出来)
本文源码下载:https://download.csdn.net/download/baidu_24578765/10490052
一、项目准备
1、新建一个MVC空项目(过程不在赘述)
2、添加SignalR引用,通过Nuget安装SignalR包。右键引用-》选择管理Nuget程序包-》在出现的窗口中输入SignalR来找到SignalR包进行安装
3、安装SignalR成功后,SignalR库的脚本将被添加进Scripts文件夹下。具体如下图所示:
4、安装Microsoft.Owin以及Microsoft.Owin.Cors,安装方式同前
二、项目构建
首先在Models文件夹下面新建一个SignalR集线器(v2)并命名为ChatsHub
然后继续新建接口IChatClient以及IChatService,在ChatsHub.cs中添加以下代码:
[HubName("ChatsHub")] public class ChatsHub : Hub<IChatClient>, IChatService { #region 基础信息 /// <summary> /// 前端自定义参数集合 /// </summary> public INameValueCollection ClientQueryString { get { return Context.QueryString; } } /// <summary> /// Cookie /// </summary> public IDictionary<string, Cookie> ClientCookies { get { return Context.RequestCookies; } } /// <summary> /// 用户信息 /// </summary> public IPrincipal ClientContextUser { get { return Context.User; } } /// <summary> /// SignalR上下文 /// </summary> public HttpContext HttpContext { get { return HttpContext.Current; } } #endregion #region 测试代码 /// <summary> /// 向所有客户端发送消息 /// </summary> /// <param name="message"></param> public async Task Send(string message) { try { //当前发送消息的用户ID,前端自定义 //string userId = ClientQueryString["userId"]; //当前连接ID string connId = Context.ConnectionId; //调用所有客户端的SendMessage方法 ChatMessageDTO msg = new ChatMessageDTO { SendId = connId, SendUserName = "", Content = message, CreateDate = DateTime.Now }; await Clients.All.SendMessage(msg); } catch (Exception e) { throw new HubException("发送消息发生异常.", new { userName = ClientContextUser.Identity.Name, message = e.Message }); } } #endregion #region 默认事件 /// <summary> /// 客户端连接的时候调用 /// </summary> /// <returns></returns> public override Task OnConnected() { //string userId = ClientQueryString["userId"]; Trace.WriteLine("客户端连接成功,连接ID是: " + Context.ConnectionId); return base.OnConnected(); } /// <summary> /// 客户端断开连接的时候调用 /// </summary> /// <param name="stopCalled"></param> /// <returns></returns> public override Task OnDisconnected(bool stopCalled) { Trace.WriteLine($"客户端[{Context.ConnectionId}]断开连接"); return base.OnDisconnected(true); } /// <summary> /// 客户端重新连接的时候调用 /// </summary> /// <returns></returns> public override Task OnReconnected() { Trace.WriteLine($"客户端[{Context.ConnectionId}]正在重新连接"); return base.OnReconnected(); } #endregion }
IChatClient.cs代码:
/// <summary> /// 客户端方法 /// </summary> public interface IChatClient { #region 用于测试 /// <summary> /// 测试方法 /// </summary> /// <param name="message"></param> /// <returns></returns> Task SendMessage(ChatMessageDTO message); #endregion }
IChatService.cs代码
/// <summary> /// 服务端方法 /// </summary> public interface IChatService { #region 基础信息 /// <summary> /// 前端自定义参数集合 /// </summary> INameValueCollection ClientQueryString { get; } /// <summary> /// Cookie /// </summary> IDictionary<string, Cookie> ClientCookies { get; } /// <summary> /// 用户信息 /// </summary> IPrincipal ClientContextUser { get; } /// <summary> /// SignalR上下文 /// </summary> HttpContext HttpContext { get; } #endregion #region 用于测试 /// <summary> /// 发送消息,测试方法 /// </summary> /// <param name="message"></param> /// <returns></returns> Task Send(string message); #endregion }
至此,一个基础的Hub就建立好了,然后继续在App_Start中新建Startup.cs,如下图:
在Startup中添加以下代码:
[assembly: OwinStartup(typeof(SignalRService.Startup))] namespace SignalRService { public class Startup { public void Configuration(IAppBuilder app) { //异常处理 GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); //自定义管道 GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); //允许跨域推送 app.UseCors(CorsOptions.AllowAll); app.MapSignalR(); } } }
添加自定义异常处理机制,新建一个ErrorHandlingPipelineModule类,这里只做了简单的实现,具体的可以根据你自己的需求进行处理:
/// <summary> /// 自定义异常处理 /// </summary> public class ErrorHandlingPipelineModule : HubPipelineModule { protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext) { Trace.WriteLine("=> Exception " + exceptionContext.Error.Message); if (exceptionContext.Error.InnerException != null) { Trace.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message); } base.OnIncomingError(exceptionContext, invokerContext); } }
新增日志处理管道LoggingPipelineModule类:
public class LoggingPipelineModule : HubPipelineModule { protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context) { Trace.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name); return base.OnBeforeIncoming(context); } protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context) { Trace.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); return base.OnBeforeOutgoing(context); } }
上述类建立好了以后,结构如下:
实体类ChatMessageDTO.cs代码:
public class ChatMessageDTO { /// <summary> /// 发送人ID /// </summary> public string SendId { get; set; } /// <summary> /// 发送方姓名 /// </summary> public string SendUserName { get; set; } /// <summary> /// 内容 /// </summary> public string Content { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime CreateDate { get; set; } }
至此,一个简单的SignalR服务端就搭建好了。接下来就是前端调用服务测试,同样,新建一个空的MVC项目,把刚刚安装SignalR时自动添加的jquery.signalR-2.2.3.min.js拷贝到现在项目中,前端实现代码:
添加一个输入框:
<div class="container"> <input type="text" id="message" /> <input type="button" id="sendmessage" value="发送消息" /> <ul id="discussion"></ul> </div>
JS代码:
<!--引用SignalR库. --> <script src="~/Scripts/jquery.signalR-2.2.3.min.js"></script> <!--引用自动生成的SignalR 集线器(Hub)脚本.在运行的时候在浏览器的Source下可看到 --> <script src="http://你的地址/signalr/hubs"></script> <script> $(function () { // 引用自动生成的集线器代理 var chat = $.connection.ChatsHub; $.connection.hub.url = 'http://你的地址/signalr/hubs'; //自定义用户ID $.connection.hub.qs = { "userId": "110" }; //启用浏览器端输出日志 //$.connection.hub.logging = true; // 定义服务器端调用的客户端SendMessage来显示新消息 chat.client.SendMessage = function (req) { // 向页面添加消息 $('#discussion').append('<li><strong>' + htmlEncode(req.SendId) + '</strong>: ' + htmlEncode(req.Content) + '</li>'); }; // 设置焦点到输入框 $('#message').focus(); // Start the connection. $.connection.hub.starting(function () { console.log('SignalR启动中...') }); $.connection.hub.received(function (e) { //console.log(e) console.log('SignalR收到消息: ') var msgBody = e.A; //console.log(msgBody) if (msgBody) console.log("用户ID:" + msgBody[0].SendId + ",消息内容:" + msgBody[0].Content) }); $.connection.hub.connectionSlow(function () { console.log('connectionSlow.') }); $.connection.hub.reconnecting(function () { console.log('SignalR正在重新连接中...') }); $.connection.hub.reconnected(function () { console.log('SignalR重新连接成功...') }); $.connection.hub.stateChanged(function (o, n) { //console.log(o) console.log('SignalR状态改变,newState:' + o.newState + ",oldState:" + o.oldState + "," + n) }); $.connection.hub.disconnected(function () { console.log('SignalR断开连接...'); setTimeout(function () { $.connection.hub.start(); }, 1000); // 1秒后重新连接 }); //错误 $.connection.hub.error(function (err) { console.log("SignalR出现错误. " + "Error: " + err.message); }); // 开始连接服务器 $.connection.hub.start().done(function () { $('#sendmessage').click(function () { var msg = $('#message').val(); // 调用服务器端集线器的Send方法 chat.server.send(msg).fail(function (e) { if (e.source === 'HubException') { console.log("异常信息:" + e.message + ",用户名:" + e.data.userName + ",错误描述:" + e.data.message); } }); // 清空输入框信息并获取焦点 $('#message').val('').focus(); }); }); }); // 为显示的消息进行Html编码 function htmlEncode(value) { var encodedValue = $('<div />').text(value).html(); return encodedValue; } </script>
在运行前,先启动服务端,然后修改js代码中服务器地址,再运行客户端,效果图如下:
三、后序
参考文章:https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server
https://www.cnblogs.com/aaaaq/p/5929104.html
特别提醒:服务端signalR版本必须与客户端版本一致。