用SignalR创建实时永久长连接异步网络应用程序

原文发表地址: Asynchronous scalable web applications with real-time persistent long-running connections with SignalR

原文发表时间: 2011-08-29 09:29

我最近在研究异步和衡量的问题。你可能看过我之前写的博文:我研究的 node.js和iisnode在Windows上运行

每个应用程序都有不同的要求,“衡量”的规则不是对每一种应用程序都适用的。衡量一个获取数据和循环的网络应用和那些召集深度潜在的主框架应用,保持服务器永久连接的应用是不一样的。

古语说“当你手上只有榔头的时候,看什么都像是钉子”,这个在编程和网络世界里的确是真理。工具越多并且掌握使用它们的技能那么效果就会越好。那也是为什么我不仅仅宣扬多种语言编程,还希望大家深入研究自己的主要语言。比如当你真正学会了LINQ,并且很擅长使用dynamic,C#就变成了一种更加有趣和富有表现力的语言。

更新是用榔头锤钉子的常见例子。想做一个聊天程序?每隔5秒更新一次。处理时间很长?那就丢掉动画图片,不停地更新,我亲爱的朋友!

间隔长一段时间来更新是另一种方法。简单来说就是打开一个连接然后保持打开状态,强制客户端(浏览器)等待,假装需要很长时间才能返回结果。如果你的服务器端程序模型上有足够多的控件,这就能允许你按照你期望的来返回数据在打开的连接上。如果连接断开了,连接会无缝地被重新打开,断开信息会在两个端口都隐藏掉。在WebSockets将会是另一种解决这类问题的方法。

ASP.NET中的永恒连接

在聊天应用程序或者股票应用程序中用ASP.NET做这样的永久连接不是很容易。在服务器或客户库中还没有一个合适的概念来讨论它。

SignalR是为ASP.NET而设的一个异步信号库。我们团队正在研究这个项目,希望能创建实时的多用户网络应用。

这不就是Socket.IO或者nowjs么?

Socket.IO是一个客户端JavaScript库,能与node.js进行交流。Nowjs是能让你从服务器端调用客户的类库。这些和Signalr都很相似而且相关,只是同一个概念中的不同方面。这些JavaScript库都希望在服务器端有特定的东西和协定,这样就有可能让服务器端的显示如同客户希望看到的那样。

SignalR是一个完全基于客户及服务器端解决方案,它是以JS作为客户端和ASP.NET作为服务端来创建这类的应用。你可以去GitHub获取。

我能用12行代码创建一个聊天应用吗?

我想说

“在代码的世界中,简洁明了永远不是神话。”

换句话说,我希望我能这么说,当然可以!

  1: Chat.DoItBaby()

但是那可能是一个谎言,下面是SignalR中的一个真实的聊天应用程序例子:

客户:

  1: var chat = $.connection.chat;
  2: chat.name = prompt("What's your name?", "");
  3:  
  4: chat.receive = function(name, message){
  5: $("#messages").append(""+name+": "+message);
  6: }
  7:  
  8: $("#send-button").click(function(){
  9: chat.distribute($("#text-input").val());
  10: });

服务器:

  1: public class Chat : Hub {
  2: public void Distribute(string message) {
  3: Clients.receive(Caller.name, message);
  4: }
  5: }

那也许是12行,其实可以缩到9行的,如果你愿意的话。

有关SignalR的更多细节

SignalR在NuGet上被分成了几个包:

· SignalR – 主要的包,包括SignalR.Server和SignalR.Js(你应该安装这个)

· SignalR.Server – 服务器端组件用以创建SignalR端点

· SignalR.Js – SignalR的Javascript客户端

· SignalR.Client – SignalR的.NET客户端

· SignalR.Ninject - SignalR 的Ninject 相关解决方案

如果你只是想了解一下玩一玩,那就从Visual Studio 2010开始。

首先,创建一个空白的ASP.NET应用程序,用NuGet安装SignalR包,用NuGet的UI或者Package Console都可以。

其次,创建一个新的default.aspx页面,添加一个按钮,一个文本框,用以下脚本来引用jQuery和jQuery.signalR。

  1: <html >
  2: <head runat="server">
  3: <script src="Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
  4: <script src="Scripts/jquery.signalR.min.js" type="text/javascript"></script>
  5: </head>
  6: <body>
  7: <form id="form1" runat="server">
  8: <div>
  9: <script type="text/javascript">
  10: $(function () {
  11: var connection = $.connection('echo');
  12: connection.received(function (data) {
  13: $('#messages').append('<li>' + data + '</li>');
  14: });
  15: connection.start();
  16: $("#broadcast").click(function () {
  17: connection.send($('#msg').val());
  18: });
  19: }); 
  20: </script>
  21: <input type="text" id="msg" />
  22: <input type="button" id="broadcast" />
  23: <ul id="messages"></ul>
  24: </div>
  25: </form>
  26: </body>
  27: </html>

底层连接

注意我们是从客户端调用/echo吗?这个在Global.asax中有所介绍:

  1: RouteTable.Routes.MapConnection<MyConnection>("echo", "echo/{*operation}");

现在,我们有两个SignalR模型来选择。我们先来看看底层的这个。

  1: using SignalR;
  2: using System.Threading.Tasks;
  3:  
  4: public class MyConnection : PersistentConnection
  5: {
  6: protected override Task OnReceivedAsync(string clientId, string data)
  7: {
  8: // Broadcast data to all clients
  9: return Connection.Broadcast(data);
  10: }
  11: }

我们继承PersistentConnection这个类,基本上可以在这层中做我们想做的任何事,这有很多个选择:

  1: public abstract class PersistentConnection : HttpTaskAsyncHandler, IGroupManager
  2: {
  3: protected ITransport _transport;
  4:  
  5: protected PersistentConnection();
  6: protected PersistentConnection(Signaler signaler, IMessageStore store, IJsonStringifier jsonStringifier);
  7:  
  8: public IConnection Connection { get; }
  9: public override bool IsReusable { get; }
  10:  
  11: public void AddToGroup(string clientId, string groupName);
  12: protected virtual IConnection CreateConnection(string clientId, IEnumerable<string> groups, HttpContextBase context);
  13: protected virtual void OnConnected(HttpContextBase context, string clientId);
  14: protected virtual Task OnConnectedAsync(HttpContextBase context, string clientId);
  15: protected virtual void OnDisconnect(string clientId);
  16: protected virtual Task OnDisconnectAsync(string clientId);
  17: protected virtual void OnError(Exception e); 
  18: protected virtual Task OnErrorAsync(Exception e);
  19: protected virtual void OnReceived(string clientId, string data);
  20: protected virtual Task OnReceivedAsync(string clientId, string data);
  21: public override Task ProcessRequestAsync(HttpContext context);
  22: public void RemoveFromGroup(string clientId, string groupName);
  23: public void Send(object value);
  24: public void Send(string clientId, object value);
  25: public void SendToGroup(string groupName, object value);
  26: }

高端中转站

或者我们可以提高一级,在添加

<script src="https://blogs.msdn.com/signalr/hubs" type="text/javascript"></script>

至页面后为我们的聊天客户做这个:

  1: $(function () {
  2: // Proxy created on the fly
  3: var chat = $.connection.chat;
  4:  
  5: // Declare a function on the chat hub so the server can invoke it
  6: chat.addMessage = function (message) {
  7: $('#messages').append('<li>' + message + '</li>');
  8: }; 
  9:  
  10: $("#broadcast").click(function () {
  11: // Call the chat method on the server
  12: chat.send($('#msg').val());
  13: });
  14:  
  15: // Start the connection
  16: $.connection.hub.start();
  17: });

然后就没有跟踪的必要了,连接聊天会映射到服务器上,然后服务器就能回调客户端了。

  1: public class Chat : Hub
  2: {
  3: public void Send(string message)
  4: {
  5: // Call the addMessage method on all clients
  6: Clients.addMessage(message);
  7: }
  8: }

我想你的脑子到现在应该要炸了。这是C#,服务器端代码,我们告诉所有的客户调用addMessage() JavaScript函数。我们通过永久连接,发送客户函数名称以获得服务器端回应,从而回调客户端。这和NowJS很像,但是没有很多人熟悉这个技术。

SignalR会处理所有客户端和服务器端的连接,确保它保持连接,运行正常。它会为你的浏览器选择正确的连接方式,用异步衡量async,await技术(就像我在node.js博文中展示过的asp.net上的可衡量异步事件 I/O )。

想亲眼看看这个样本的运行?

我们在https://chatapp.apphb.com上有一个小小的聊天应用程序,快去看看吧。那里有aspnet的同仁。试试粘贴到你的YouTube或相册上吧!

image

这是早期的,不过还是一个很有趣的.NET新组件,以前也从没出现过。你可以随意去GitHub看看,和SignalR的作者David Fowler和Damian Edwards交流下。希望你们喜欢。