SignalR -- server push 利器
实际上关于SignalR的介绍网上有很多,这里不做过多赘述,我们来看下官方网站的描述。
【摘录自http://signalr.net/】
What is ASP.NET SignalR
ASP.NET SignalR is a new library for ASP.NET developers that makes it incredibly simple to add real-time web functionality to your applications. What is "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time.
You may have heard of WebSockets, a new HTML5 API that enables bi-directional communication between the browser and server. SignalR will use WebSockets under the covers when it's available, and gracefully fallback to other techniques and technologies when it isn't, while your application code stays the same.
SignalR also provides a very simple, high-level API for doing server to client RPC (call JavaScript functions in your clients' browsers from server-side .NET code) in your ASP.NET application, as well as adding useful hooks for connection management, e.g. connect/disconnect events, grouping connections, authorization.
What can you do with ASP.NET SignalR?
SignalR can be used to add any sort of "real-time" web functionality to your ASP.NET application. While chat is often used as an example, you can do a whole lot more. Any time a user refreshes a web page to see new data, or the page implements Ajax long polling to retrieve new data, is candidate for using SignalR.
It also enables completely new types of applications, that require high frequency updates from the server, e.g. real-time gaming. For a great example of this see the ShootR game(作者:这个游戏很给力)
实际上很早以前我就有过使用server push技术的想法,不过一直没有实现,总是有一些问题。一般来说原理就是一个长连接,好比在client与server之间建立一个通信通道,维护请求与响应,我们都知道,请求之后响应就会回来,但是如果我们把请求hold 住, 不撒手, 当server需要push时,在把这个响应推回给client。SignalR对这部分处理的相当精彩。
我们来创建一个mvc4的项目,通过NuGet来获取SignalR的所需组件。
对于SignalR来说,他需要将自身的hub进行注册,也就是说需要注册到路由中,这个hub可以理解为分发器或者推送器。
所以,创建RegisterHubs.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Routing; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Web.Mvc.SingalRDemo.App_Start.RegisterHubs), "Start")] namespace Web.Mvc.SingalRDemo.App_Start { public class RegisterHubs { public static void Start() { // Register the default hubs route: ~/signalr/hubs RouteTable.Routes.MapHubs(); } } }
其中需要WebActivatorEx来进行注入,所以需要获得这个组件。
第二,我们来组织一下页面,并不复杂,
创建一个controller,就叫做home吧,
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Web.Mvc.SingalRDemo.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } } }
第三,创建view,
@model dynamic @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2> Index</h2> my name : <span></span> <br /> <select id="userSelect"> </select><input type="text" id="message" /><input type="button" value="send" id="send" /> <div id="braod"> </div>
第四,修改BundleConfig,
bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js", "~/Scripts/jquery.signalR-1.0.1.js", "~/Scripts/modernizr-2.5.3.js" ));
第五,修改_Layout.cshtml,
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> @RenderBody() @Scripts.Render("~/bundles/jquery") <script src="../signalr/hubs"></script> <script src="~/Scripts/demo.js"></script> @RenderSection("scripts", required: false) </body> </html>
这里要注意,<script src="../signalr/hubs"></script>这句是一定要加的,而且需要加在 jquery.signalR-1.0.1.js的下面。大家也会发现,在下面会有一个自己的demo.js,为了方便就放这里了。
第六,来创建hub,
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; namespace Web.Mvc.SingalRDemo.Models { [HubName("chat")] public class ChatHub : Hub { private static List<User> users = new List<User>(); public void send(string name, string message) { Clients.All.push(name, message); } public void sendTo(string from, string to, string message) { var user = users.Where(u => u.Id == to).SingleOrDefault(); if (user != null) { Clients.Client(to).pushOne(from, message); } } public void bind() { User u = new User() { Name = "user - " + Guid.NewGuid(), Id = Context.ConnectionId }; users.Add(u); Clients.All.bindList(users); Clients.Client(u.Id).setName(u.Name); } public override System.Threading.Tasks.Task OnConnected() { return base.OnConnected(); } public override System.Threading.Tasks.Task OnDisconnected() { users.Remove(users.Where(u => u.Id == Context.ConnectionId).SingleOrDefault()); return base.OnDisconnected(); } } }
这里说几句,HubName跟actionName类似。在hub中,定义了三个服务器端的方法,用来被客户端调用,而在这些方法中也有一些方法并没被定义,这个是需要在demo.js中被定义的,用来被服务器端调用。
Clients.All,这句相当于广播。
Clients.Client(to),这句相当于知道了对方的ConnectionId,来进行特定点的推送。
这里逻辑并不复杂。
附带上user的代码,
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace Web.Mvc.SingalRDemo.Models { public class User { public string Id { get; set; } public string Name { get; set; } } }
第七,来创建demo.js和hub进行沟通,
$(function () { var chat = $.connection.chat; $.extend(chat.client, { push: function (a, b) { $("#braod").append("<li>" + a + " : " + b + "</li>"); }, pushOne: function (a, b) { $("#braod").append("<li>" + a + " say to you : " + b + "</li>"); }, bindList: function (result) { $('#userSelect').empty(); $('#userSelect').append("<option value='All'>All</option>"); $.each(result, function (index, u) { $('#userSelect').append("<option value='" + u.Id + "'>" + u.Name + "</option>"); }); }, setName:function(name) { $('span').text(name); } }); $.connection.hub.start().done(function () { refreshNaleList(); }); $("#send").click(function () { var select = $('#userSelect'); if (select.val() == 'All') { chat.server.send($("span").text(), $("#message").val()); } else { chat.server.sendTo($("span").text(), select.val(), $("#message").val()); } }); function refreshNaleList() { chat.server.bind(); } });
这里简单说几句,var chat = $.connection.chat;相当于获得hub对象,由他来建立通道。
chat.client这里,来给client注册一些方法,如果服务器端进行调用这些方法,那么客户端就会执行,参数神马的一看就明白了。
chat.server,服务器端的方法通过这种形式进行调用。
这样就会得到一方有动作,所有连接的客户端都会得到消息,而不用进行轮询。
具体效果,跑起来就明白了,程序挺简单。
最后,我和同事进行了测试,如果在有3方连接,在操作过程中,其中一方掉线了(我们拔掉了网线),此时我客户端发消息,服务器端得到消息进行推送,推送完成后,掉线一方插上网线,过了一会,消息就被推送过来了,看起来他在服务器端进行了一些信息的维护,估计是有一个时间的,超过一个时间连接就失效了。这一点很厉害。不用自己维护了。
Ok,it is that.