用SignalR建立實時永久長連線非同步網路應用程式

adagadadfafd發表於2012-09-06

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

我最近在研究非同步和衡量的問題。你可能看過我之前寫的博文:我研究的 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="http://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 )。

想親眼看看這個樣本的執行?

我們在http://chatapp.apphb.com上有一個小小的聊天應用程式,快去看看吧。那裡有aspnet的同仁。試試貼上到你的YouTube或相簿上吧!


這是早期的,不過還是一個很有趣的.NET新元件,以前也從沒出現過。你可以隨意去GitHub看看,和SignalR的作者David Fowler和Damian Edwards交流下。希望你們喜歡。

相關文章