向項目中添加一個SignalR集線器(v2)並命名為ServerHub。
將下面代碼填充到剛剛創建的ServerHub類中。
創建一個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 } } Startup Code在Home控制器中創建一個Chat Action方法
1 using System.Web.Mvc; 2 3 namespace SignalDemo.Controllers 4 { 5 public class HomeController : Controller 6 { 7 public ActionResult Chat() 8 { 9 return View(); 10 } 11 } 12 } Home Controller在Views文件中Home文件夾中創建一個Chat視圖,視圖代碼如下所示:
1 @{ 2 ViewBag.Title = "Chat"; 3 } 4 5 <h2>Chat</h2> 6 7 <div class="container"> 8 <input type="text" id="message" /> 9 <input type="button" id="sendmessage" value="Send" /> 10 <input type="hidden" id="displayname" /> 11 <ul id="discussion"></ul> 12 </div> 13 14 @section scripts 15 { 16 <!--引用SignalR庫. --> 17 <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script> 18 <!--引用自動生成的SignalR 集線器(Hub)腳本.在運行的時候在浏覽器的Source下可看到 --> 19 <script src="~/signalr/hubs"></script> 20 <script> 21 $(function () { 22 // 引用自動生成的集線器代理 23 var chat = $.connection.serverHub; 24 // 定義服務器端調用的客戶端sendMessage來顯示新消息 25 26 chat.client.sendMessage = function (name, message) { 27 // 向頁面添加消息 28 $('#discussion').append('<li><strong>' + htmlEncode(name) 29 + '</strong>: ' + htmlEncode(message) + '</li>'); 30 }; 31 // 設置焦點到輸入框 32 $('#message').focus(); 33 // 開始連接服務器 34 $.connection.hub.start().done(function () { 35 $('#sendmessage').click(function () { 36 // 調用服務器端集線器的Send方法 37 chat.server.send($('#message').val()); 38 // 清空輸入框信息並獲取焦點 39 $('#message').val('').focus(); 40 }); 41 }); 42 }); 43 44 // 為顯示的消息進行Html編碼 45 function htmlEncode(value) { 46 var encodedValue = $('<div />').text(value).html(); 47 return encodedValue; 48 } 49 </script> 50 } View Chat Code修改App_Start文件夾內的RoutConfig類,將Action方法默認設置為Chat
1 using System.Web.Mvc; 2 using System.Web.Routing; 3 4 namespace SignalDemo 5 { 6 public class RouteConfig 7 { 8 public static void RegisterRoutes(RouteCollection routes) 9 { 10 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 routes.MapRoute( 12 name: "Default", 13 url: "{controller}/{action}/{id}", 14 defaults: new { controller = "Home", action = "Chat", id = UrlParameter.Optional } 15 ); 16 } 17 } 18 } RouteConfig Code到此,我們的例子就實現完成了,接下來我們先來看看運行效果,之後再來解釋到底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 public class ServerHub : Hub 2 { 3 private static readonly char[] Constant = 4 { 5 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 6 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 7 'w', 'x', 'y', 'z', 8 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 9 'W', 'X', 'Y', 'Z' 10 }; 11 12 /// <summary> 13 /// 供客戶端調用的服務器端代碼 14 /// </summary> 15 /// <param name="message"></param> 16 public void Send(string message) 17 { 18 var name = GenerateRandomName(4); 19 20 // 調用所有客戶端的sendMessage方法 21 Clients.All.sendMessage(name, message); 22 } 23 24 /// <summary> 25 /// 客戶端連接的時候調用 26 /// </summary> 27 /// <returns></returns> 28 public override Task OnConnected() 29 { 30 Trace.WriteLine("客戶端連接成功"); 31 return base.OnConnected(); 32 } 33 34 /// <summary> 35 /// 產生隨機用戶名函數 36 /// </summary> 37 /// <param name="length">用戶名長度</param> 38 /// <returns></returns> 39 public static string GenerateRandomName(int length) 40 { 41 var newRandom = new System.Text.StringBuilder(62); 42 var rd = new Random(); 43 for (var i = 0; i < length; i++) 44 { 45 newRandom.Append(Constant[rd.Next(62)]); 46 } 47 return newRandom.ToString(); 48 } 49 }
這樣在運行頁面的時候,將在輸出窗口看到“客戶端連接成功”字樣。運行效果如下圖所示:
在第二部分介紹的時候說道,在服務端聲明的所有Hub信息,都會生成JavaScript輸出到客戶端,為了驗證這一點,可以在Chrome中F12來查看源碼就明白了,具體如下圖所示:
看到上圖,你也就明白了為什麼Chat.cshtml頁面需要引入"signalr/hubs"腳本庫了吧。
1 <!--引用SignalR庫. --> 2 <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> 3 <!--引用自動生成的SignalR 集線器(Hub)腳本.在運行的時候在浏覽器的Source下可看到 --> 4 <script src="~/signalr/hubs"></script> 5
上面部分介紹了SignalR在Asp.net MVC 中的實現,這部分將通過一個例子來看看SignalR在WPF或WinForm是如何使用的。其實這部分實現和Asp.net MVC中非常相似,主要不同在於,Asp.net MVC中的SignalR服務器寄宿在IIS中,而在WPF中應用,我們把SignalR寄宿在WPF客戶端中。
下面讓我們看看SignalR服務端的實現。
1 /// <summary> 2 /// 啟動SignalR服務,將SignalR服務寄宿在WPF程序中 3 /// </summary> 4 private void StartServer() 5 { 6 try 7 { 8 SignalR = WebApp.Start(ServerUri); // 啟動SignalR服務 9 } 10 catch (TargetInvocationException) 11 { 12 WriteToConsole("一個服務已經運行在:" + ServerUri); 13 // Dispatcher回調來設置UI控件狀態 14 this.Dispatcher.Invoke(() => ButtonStart.IsEnabled = true); 15 return; 16 } 17 18 this.Dispatcher.Invoke(() => ButtonStop.IsEnabled = true); 19 WriteToConsole("服務已經成功啟動,地址為:" + ServerUri); 20 } 21 22 public class ChatHub : Hub 23 { 24 public void Send(string name, string message) 25 { 26 Clients.All.addMessage(name, message); 27 } 28 29 public override Task OnConnected() 30 { 31 // 32 Application.Current.Dispatcher.Invoke(() => 33 ((MainWindow)Application.Current.MainWindow).WriteToConsole("客戶端連接,連接ID是: " + Context.ConnectionId)); 34 35 return base.OnConnected(); 36 } 37 38 public override Task OnDisconnected(bool stopCalled) 39 { 40 Application.Current.Dispatcher.Invoke(() => 41 ((MainWindow)Application.Current.MainWindow).WriteToConsole("客戶端斷開連接,連接ID是: " + Context.ConnectionId)); 42 43 return base.OnDisconnected(true); 44 } 45 } 46 47 public class Startup 48 { 49 public void Configuration(IAppBuilder app) 50 { 51 // 有關如何配置應用程序的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkID=316888 52 // 允許CORS跨域 53 //app.UseCors(CorsOptions.AllowAll); 54 app.MapSignalR(); 55 } 56 } View Code通過上面的代碼,我們SignalR服務端的實現就完成了,其實現邏輯與Asp.net MVC的代碼類似。
接下來,讓我們看看,WPF客戶端是如何連接和與服務器進行通信的。具體客戶端的實現如下:
1 public IHubProxy HubProxy { get; set; } 2 const string ServerUri = "http://localhost:8888/signalr"; 3 public HubConnection Connection { get; set; } 4 5 public MainWindow() 6 { 7 InitializeComponent(); 8 9 // 窗口啟動時開始連接服務 10 ConnectAsync(); 11 } 12 13 /// <summary> 14 /// 發送消息 15 /// </summary> 16 /// <param name="sender"></param> 17 /// <param name="e"></param> 18 private void ButtonSend_Click(object sender, RoutedEventArgs e) 19 { 20 // 通過代理來調用服務端的Send方法 21 // 服務端Send方法再調用客戶端的AddMessage方法將消息輸出到消息框中 22 HubProxy.Invoke("Send", GenerateRandomName(4), TextBoxMessage.Text.Trim()); 23 24 TextBoxMessage.Text = String.Empty; 25 TextBoxMessage.Focus(); 26 } 27 28 private async void ConnectAsync() 29 { 30 Connection = new HubConnection(ServerUri); 31 Connection.Closed += Connection_Closed; 32 33 // 創建一個集線器代理對象 34 HubProxy = Connection.CreateHubProxy("ChatHub"); 35 36 // 供服務端調用,將消息輸出到消息列表框中 37 HubProxy.On<string, string>("AddMessage", (name, message) => 38 this.Dispatcher.Invoke(() => 39 RichTextBoxConsole.AppendText(String.Format("{0}: {1}\r", name, message)) 40 )); 41 42 try 43 { 44 await Connection.Start(); 45 } 46 catch (HttpRequestException) 47 { 48 // 連接失敗 49 return; 50 } 51 52 // 顯示聊天控件 53 ChatPanel.Visibility = Visibility.Visible; 54 ButtonSend.IsEnabled = true; 55 TextBoxMessage.Focus(); 56 RichTextBoxConsole.AppendText("連上服務:" + ServerUri + "\r"); 57 } View Code上面的代碼也就是WPF客戶端實現的核心代碼,主要邏輯為,客戶端啟動的時候就調用Connection.Start方法與服務器進行連接。然後通過HubProxy代理類來調用集線器中Send方法,而集線器中的Send方法又通過調用客戶端的addMessage方法將消息輸出到客戶端的消息框中進行顯示,從而完成消息的推送過程。接下來,讓我們看看其運行效果:
從上面的運行效果看出,其效果和Asp.net MVC上的效果是一樣的。
到這裡,本專題的所有內容就結束了,這篇SignalR快速入門也是本人在學習SignalR過程中的一些心得體會,希望可以幫助一些剛接觸SignalR的朋友快速入門。本篇主要實現了SignalR的廣播消息的功能,可以實現手機端消息推送的功能,接下來一篇將介紹如何使用SignalR實現一對一的聊天。