一、簡介
Signal 是微軟支援的一個運行在 Dot NET 平台上的 html websocket 架構。它出現的主要目的是實現伺服器主動推送(Push)訊息到用戶端頁面,這樣用戶端就不必重新發送請求或使用輪詢技術來擷取訊息。
可訪問其官方網站:https://github.com/SignalR/ 擷取更多資訊。
二、Asp.net SignalR 是個什麼東東
Asp.net SignalR是微軟為實現即時通訊的一個類庫。一般情況下,SignalR會使用JavaScript的長輪詢(long polling)的方式來實現用戶端和伺服器通訊,隨著Html5中WebSockets出現,SignalR也支援WebSockets通訊。另外SignalR開發的程式不僅僅限制於宿主在IIS中,也可以宿主在任何應用程式,包括控制台,用戶端程式和Windows服務等,另外還支援Mono,這意味著它可以實現跨平台部署在Linux環境下。
SignalR內部有兩類對象:
Http持久串連(Persisten Connection)對象:用來解決長時間串連的功能。還可以由用戶端主動向伺服器要求資料,而伺服器端不需要實現太多細節,只需要處理PersistentConnection 內所提供的五個事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。
Hub(集線器)對象:用來解決即時(realtime)資訊交換的功能,服務端可以利用URL來註冊一個或多個Hub,只要串連到這個Hub,就能與所有的用戶端共用發送到伺服器上的資訊,同時服務端可以調用用戶端的指令碼。
SignalR將整個資訊的交換封裝起來,用戶端和伺服器都是使用JSON來溝通的,在服務端聲明的所有Hub資訊,都會產生JavaScript輸出到用戶端,.NET則依賴Proxy來組建代理程式對象,而Proxy的內部則是將JSON轉換成對象。
SignalR既然是為即時而生的,這樣就決定了其使用場所。具體適用情景有如下幾點:
聊天室,如線上客服系統,IM系統等
股票價格即時更新
訊息的推送服務
遊戲中人物位置的即時推送
目前,我所在公司在開發的就是線上客服系統。
三、實現機制
SignalR 的實現機制與 .NET WCF 或 Remoting 是相似的,都是使用遠程代理來實現。在具體使用上,有兩種不同目的的介面:PersistentConnection 和 Hubs,其中 PersistentConnection 是實現了長時間的 Javascript 輪詢(類似於 Comet),Hub 是用來解決即時資訊交換問題,它是利用 Javascript 動態載入執行方法實現的。SignalR 將整個串連,資訊交換過程封裝得非常漂亮,用戶端與伺服器端全部使用 JSON 來交換資料。
下面就 Hubs 介面的使用來講講整個流程:
1.在伺服器端定義對應的 hub class;
2.在用戶端定義 hub class 所對應的 proxy 類;
3.在用戶端與伺服器端建立串連(connection);
4.然後用戶端就可以調用 proxy 對象的方法來調用伺服器端的方法,也就是發送 request 給伺服器端;
5.伺服器端接收到 request 之後,可以針對某個/組用戶端或所有用戶端(廣播)發送訊息。
四、使用Asp.net SignalR在Web端實現廣播訊息
通過第二部分的介紹,相信大家對Asp.net SignalR有了一個初步的瞭解,接下來通過兩個例子來讓大家加深對SignalR運行機制的理解。第一個例子就是在Web端如何使用SignalR來實現廣播訊息。
使用Visual Studio 2013,建立一個MVC工程
通過Nuget安裝SignalR包。右鍵引用-》選擇管理Nuget程式包-》在出現的視窗中輸入SignalR來找到SignalR包進行安裝。
安裝SignalR成功後,SignalR庫的指令碼將被添加進Scripts檔案夾下。具體如下圖所示:
向項目中添加一個SignalR集線器(v2)並命名為ServerHub。
將下面代碼填充到剛剛建立的ServerHub類中。
using System;using Microsoft.AspNet.SignalR;namespace SignalDemo{ public class ServerHub : Hub { private static readonly char[] Constant = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; /// <summary> /// 供用戶端調用的伺服器端代碼 /// </summary> /// <param name="message"></param> public void Send(string message) { var name = GenerateRandomName(4); // 調用所有用戶端的sendMessage方法 Clients.All.sendMessage(name, message); } /// <summary> /// 產生隨機使用者名稱函數 /// </summary> /// <param name="length">使用者名稱長度</param> /// <returns></returns> public static string GenerateRandomName(int length) { var newRandom = new System.Text.StringBuilder(62); var rd = new Random(); for (var i = 0; i < length; i++) { newRandom.Append(Constant[rd.Next(62)]); } return newRandom.ToString(); } }}
建立一個Startup類,如果開始建立MVC項目的時候沒有更改身分識別驗證的話,這個類會預設添加的,如果已有就不需要重複添加了。按照如下代碼更新Startup類。
using Microsoft.Owin;using Owin;[assembly: OwinStartupAttribute(typeof(SignalDemo.Startup))]namespace SignalDemo{ public partial class Startup { #region MyRegion public void Configuration(IAppBuilder app) { app.MapSignalR(); ConfigureAuth(app); } #endregion }}
在Home控制器中建立一個Chat Action方法
using System.Web.Mvc;namespace SignalDemo.Controllers{ public class HomeController : Controller { public ActionResult Chat() { return View(); } }}
在Views檔案中Home檔案夾中建立一個Chat視圖,視圖代碼如下所示:
@{ ViewBag.Title = "Chat";}<h2>Chat</h2><div class="container"> <input type="text" id="message" /> <input type="button" id="sendmessage" value="Send" /> <input type="hidden" id="displayname" /> <ul id="discussion"></ul></div>@section scripts{ <!--引用SignalR庫. --> <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script> <!--引用自動產生的SignalR 集線器(Hub)指令碼.在啟動並執行時候在瀏覽器的Source下可看到 --> <script src="~/signalr/hubs"></script> <script> $(function () { // 引用自動產生的集線器代理 var chat = $.connection.serverHub; // 定義伺服器端調用的用戶端sendMessage來顯示新訊息 chat.client.sendMessage = function (name, message) { // 向頁面添加訊息 $('#discussion').append('<li><strong>' + htmlEncode(name) + '</strong>: ' + htmlEncode(message) + '</li>'); }; // 設定焦點到輸入框 $('#message').focus(); // 開始串連伺服器 $.connection.hub.start().done(function () { $('#sendmessage').click(function () { // 調用伺服器端集線器的Send方法 chat.server.send($('#message').val()); // 清空輸入框資訊並擷取焦點 $('#message').val('').focus(); }); }); }); // 為顯示的訊息進行Html編碼 function htmlEncode(value) { var encodedValue = $('<div />').text(value).html(); return encodedValue; } </script>}
修改App_Start檔案夾內的RoutConfig類,將Action方法預設設定為Chat
using System.Web.Mvc;using System.Web.Routing;namespace SignalDemo{ public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Chat", id = UrlParameter.Optional } ); } }}
到此,我們的例子就實現完成了,接下來我們先來看看運行效果,之後再來解釋到底SignalR是如何來完成廣播訊息的。啟動並執行運行結果如下。
從運行結果,你可以發現,在任何一個視窗輸入資訊並發送,所有用戶端將收到該訊息。這樣的效果在實際應用中很多,如QQ,一登入QQ的時候都會推送騰訊廣告訊息。
看完了運行結果,接下來我們來分析下代碼,進而來剖析下SignalR到底是如何工作的。
按照B/S模式來看,運行程式的時候,Web頁面就與SignalR的服務建立了串連,具體的建立串連的代碼就是:$.connection.hub.start()。這句代碼的作用就是與SignalR服務建立串連,後面的done函數表明建立串連成功後為發送按鈕註冊了一個click事件,當用戶端輸入內容點擊發送按鈕後,該Click事件將會觸發,觸發執行的操作為: chat.server.send($('#message').val())。這句代碼錶示調用服務端的send函數,而服務端的Send韓式又是調用所有用戶端的sendMessage函數,而用戶端中sendMessage函數就是將資訊添加到對應的訊息列表中。這樣就實現了廣播訊息的功能了。
看到這裡,有人是否會有疑問,前面的實現都只用到了集線器對象,而沒有用到持久連線物件。其實並不是如此,$.connection這句代碼就是使用持久連線物件,當然你也可以在重新OnConnected方法來查看監控用戶端的串連情況,更新的代碼如下所示:
public class ServerHub : Hub { private static readonly char[] Constant = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; /// <summary> /// 供用戶端調用的伺服器端代碼 /// </summary> /// <param name="message"></param> public void Send(string message) { var name = GenerateRandomName(4); // 調用所有用戶端的sendMessage方法 Clients.All.sendMessage(name, message); } /// <summary> /// 用戶端串連的時候調用 /// </summary> /// <returns></returns> public override Task OnConnected() { Trace.WriteLine("用戶端串連成功"); return base.OnConnected(); } /// <summary> /// 產生隨機使用者名稱函數 /// </summary> /// <param name="length">使用者名稱長度</param> /// <returns></returns> public static string GenerateRandomName(int length) { var newRandom = new System.Text.StringBuilder(62); var rd = new Random(); for (var i = 0; i < length; i++) { newRandom.Append(Constant[rd.Next(62)]); } return newRandom.ToString(); } }
這樣在運行頁面的時候,將在輸出視窗看到“用戶端串連成功”字樣。運行效果如下圖所示:
在第二部分介紹的時候說道,在服務端聲明的所有Hub資訊,都會產生JavaScript輸出到用戶端,為了驗證這一點,可以在Chrome中F12來查看源碼就明白了,具體如下圖所示:
看到上圖,你也就明白了為什麼Chat.cshtml頁面需要引入"signalr/hubs"指令碼庫了吧。
<!--引用SignalR庫. -->
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<!--引用自動產生的SignalR 集線器(Hub)指令碼.在啟動並執行時候在瀏覽器的Source下可看到 -->
<script src="~/signalr/hubs"></script>
五、在傳統型程式中如何使用Asp.net SignalR
上面部分介紹了SignalR在Asp.net MVC 中的實現,這部分將通過一個例子來看看SignalR在WPF或WinForm是如何使用的。其實這部分實現和Asp.net MVC中非常相似,主要不同在於,Asp.net MVC中的SignalR伺服器寄宿在IIS中,而在WPF中應用,我們把SignalR寄宿在WPF用戶端中。
下面讓我們看看SignalR服務端的實現。
/// <summary> /// 啟動SignalR服務,將SignalR服務寄宿在WPF程式中 /// </summary> private void StartServer() { try { SignalR = WebApp.Start(ServerUri); // 啟動SignalR服務 } catch (TargetInvocationException) { WriteToConsole("一個服務已經運行在:" + ServerUri); // Dispatcher回調來設定UI控制項狀態 this.Dispatcher.Invoke(() => ButtonStart.IsEnabled = true); return; } this.Dispatcher.Invoke(() => ButtonStop.IsEnabled = true); WriteToConsole("服務已經成功啟動,地址為:" + ServerUri); }public class ChatHub : Hub { public void Send(string name, string message) { Clients.All.addMessage(name, message); } public override Task OnConnected() { // Application.Current.Dispatcher.Invoke(() => ((MainWindow)Application.Current.MainWindow).WriteToConsole("用戶端串連,串連ID是: " + Context.ConnectionId)); return base.OnConnected(); } public override Task OnDisconnected(bool stopCalled) { Application.Current.Dispatcher.Invoke(() => ((MainWindow)Application.Current.MainWindow).WriteToConsole("用戶端中斷連線,串連ID是: " + Context.ConnectionId)); return base.OnDisconnected(true); } } public class Startup { public void Configuration(IAppBuilder app) { // 有關如何配置應用程式的詳細資料,請訪問 http://go.microsoft.com/fwlink/?LinkID=316888 // 允許CORS跨域 //app.UseCors(CorsOptions.AllowAll); app.MapSignalR(); } }
通過上面的代碼,我們SignalR服務端的實現就完成了,其實現邏輯與Asp.net MVC的代碼類似。
接下來,讓我們看看,WPF用戶端是如何串連和與伺服器進行通訊的。具體用戶端的實現如下:
public IHubProxy HubProxy { get; set; } const string ServerUri = "http://localhost:8888/signalr"; public HubConnection Connection { get; set; } public MainWindow() { InitializeComponent(); // 視窗啟動時開始串連服務 ConnectAsync(); } /// <summary> /// 發送訊息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ButtonSend_Click(object sender, RoutedEventArgs e) { // 通過代理來調用服務端的Send方法 // 服務端Send方法再調用用戶端的AddMessage方法將訊息輸出到訊息框中 HubProxy.Invoke("Send", GenerateRandomName(4), TextBoxMessage.Text.Trim()); TextBoxMessage.Text = String.Empty; TextBoxMessage.Focus(); } private async void ConnectAsync() { Connection = new HubConnection(ServerUri); Connection.Closed += Connection_Closed; // 建立一個集線器代理對象 HubProxy = Connection.CreateHubProxy("ChatHub"); // 供服務端調用,將訊息輸出到訊息列表框中 HubProxy.On<string, string>("AddMessage", (name, message) => this.Dispatcher.Invoke(() => RichTextBoxConsole.AppendText(String.Format("{0}: {1}\r", name, message)) )); try { await Connection.Start(); } catch (HttpRequestException) { // 串連失敗 return; } // 顯示聊天控制項 ChatPanel.Visibility = Visibility.Visible; ButtonSend.IsEnabled = true; TextBoxMessage.Focus(); RichTextBoxConsole.AppendText("連上服務:" + ServerUri + "\r"); }
上面的代碼也就是WPF用戶端實現的核心代碼,主要邏輯為,用戶端啟動的時候就調用Connection.Start方法與伺服器進行串連。然後通過HubProxy代理類來調用集線器中Send方法,而集線器中的Send方法又通過調用用戶端的addMessage方法將訊息輸出到用戶端的訊息框中進行顯示,從而完成訊息的推送過程。接下來,讓我們看看其運行效果:
從上面的運行效果看出,其效果和Asp.net MVC上的效果是一樣的。
總結
到這裡,本專題的所有內容就結束了,這篇SignalR快速入門也是本人在學習SignalR過程中的一些心得體會,希望可以協助一些剛接觸SignalR的朋友快速入門。本篇主要實現了SignalR的廣播訊息的功能,可以實現手機端訊息推送的功能,接下來一篇將介紹如何使用SignalR實現一對一的聊天。
源碼下載:http://xiazai.jb51.net/201609/yuanma/ASP.NETSignalDemo.rar
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。