Asp.NET MVC 中使用 SignalR 實現推送功能


  • 一,簡介

  • 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類中。

    •  1 using System;
       2 using Microsoft.AspNet.SignalR;
       3 
       4 namespace SignalDemo
       5 {
       6     public class ServerHub : Hub
       7     {
       8         private static readonly char[] Constant =
       9         {
      10             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      11             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
      12             'w', 'x', 'y', 'z',
      13             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
      14             'W', 'X', 'Y', 'Z'
      15         };
      16 
      17         /// <summary>
      18         /// 供客戶端調用的服務器端代碼
      19         /// </summary>
      20         /// <param name="message"></param>
      21         public void Send(string message)
      22         {
      23             var name = GenerateRandomName(4);
      24 
      25             // 調用所有客戶端的sendMessage方法
      26             Clients.All.sendMessage(name, message);
      27         }
      28 
      29         /// <summary>
      30         /// 產生隨機用戶名函數
      31         /// </summary>
      32         /// <param name="length">用戶名長度</param>
      33         /// <returns></returns>
      34         public static string GenerateRandomName(int length)
      35         {
      36             var newRandom = new System.Text.StringBuilder(62);
      37             var rd = new Random();
      38             for (var i = 0; i < length; i++)
      39             {
      40                 newRandom.Append(Constant[rd.Next(62)]);
      41             }
      42             return newRandom.ToString();
      43         }
      44     }
      45 }
      ServerHub Code

      創建一個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     

       

  • 五、在桌面程序中如何使用Asp.net SignalR

       上面部分介紹了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實現一對一的聊天。

  • 本文所有源碼:鏈接:http://pan.baidu.com/s/1jHXcW8Q 密碼:1otz


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM