今天的專題就是讓大家可以快速的上手Asp.net 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既然是為實時而生的,這樣就決定了其使用場所。具體適用情景有如下幾點:
1、聊天室,如在線客服系統,IM系統等
2、股票價格實時更新
3、消息的推送服務
4、游戲中人物位置的實時推送
目前,我所在公司在開發的就是在線客服系統。
三、使用Asp.net SignalR在Web端實現廣播消息
通過第二部分的介紹,相信大家對Asp.net SignalR有了一個初步的了解,接下來通過兩個例子來讓大家加深對SignalR運行機制的理解。第一個例子就是在Web端如何使用SignalR來實現廣播消息。
使用Visual Studio 2013,創建一個MVC工程
通過Nuget安裝SignalR包。右鍵引用-》選擇管理Nuget程序包-》在出現的窗口中輸入SignalR來找到SignalR包進行安裝。
安裝SignalR成功后,SignalR庫的腳本將被添加進Scripts文件夾下。具體如下圖所示:
4. 向項目中添加一個SignalR集線器(v2)并命名為ServerHub。
5. 將下面代碼填充到剛剛創建的ServerHub類中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using System; namespace SignalRQuickStart { 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(); } } } |
6. 創建一個Startup類,如果開始創建MVC項目的時候沒有更改身份驗證的話,這個類會默認添加的,如果已有就不需要重復添加了。按照如下代碼更新Startup類。
7. 在Home控制器中創建一個Home Action方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { ViewBag.Message = "Your application description page." ; return View(); } public ActionResult Contact() { ViewBag.Message = "Your contact page." ; return View(); } public ActionResult Chat() { return View(); } } |
8. 在Views文件中Home文件夾中創建一個Chat視圖,視圖代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
@{ ViewBag.Title = "聊天窗口" ; } <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.0.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> } |
9. 修改App_Start文件夾內的RoutConfig類,將Action方法默認設置為Chat.
1
2
3
4
5
6
7
8
9
10
11
12
|
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方法來查看監控客戶端的連接情況,更新的代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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"腳本庫了吧。
1
2
3
4
|
<!--引用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服務端的實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
/// <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客戶端是如何連接和與服務器進行通信的。具體客戶端的實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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實現一對一的聊天。