共用方式為


使用 SignalR 1.x 進行高頻率即時聊天

作者 :Patrick Fletcher

警告

本檔不適用於最新版的 SignalR。 看看ASP.NET Core SignalR

本教學課程說明如何建立使用 ASP.NET SignalR 提供高頻率傳訊功能的 Web 應用程式。 在此情況下,高頻率傳訊表示以固定速率傳送的更新;在此應用程式案例中,每秒最多 10 則訊息。

您將在本教學課程中建立的應用程式會顯示使用者可以拖曳的圖形。 圖形在所有其他連接的瀏覽器中的位置將會更新,以符合使用計時更新的拖曳圖形位置。

本教學課程仲介紹的概念具有即時遊戲和其他模擬應用程式的應用程式。

歡迎使用教學課程的批註。 如果您有與教學課程不直接相關的問題,您可以將問題張貼至 ASP.NET SignalR 論壇StackOverflow.com

概觀

本教學課程示範如何建立應用程式,以即時與其他瀏覽器共用物件的狀態。 我們將建立的應用程式稱為 MoveShape。 MoveShape 頁面會顯示使用者可以拖曳的 HTML Div 元素;當使用者拖曳 Div 時,其新位置會傳送至伺服器,然後告訴所有其他連線的用戶端更新圖形的位置以符合。

顯示 MoveShape 應用程式頁面的螢幕擷取畫面。

本教學課程中建立的應用程式是以 Damian Edwards 的示範為基礎。 您可以在這裡看到包含此示範的影片。

本教學課程會從示範如何從拖曳圖形時引發的每個事件傳送 SignalR 訊息開始。 每次收到訊息時,每個連接的用戶端都會更新圖形本機版本的位置。

雖然應用程式會使用這個方法運作,但這不是建議的程式設計模型,因為傳送的訊息數目不會有任何上限,因此用戶端和伺服器可能會因為訊息和效能而無法負荷。 用戶端上的顯示動畫也會脫離,因為圖形會立即由每個方法移動,而不是順暢地移至每個新位置。 本教學課程稍後的章節將示範如何建立計時器函式,以限制用戶端或伺服器傳送訊息的最大速率,以及如何在位置之間順暢地移動圖形。 本教學課程中建立的應用程式最終版本可從 程式碼庫下載。

本教學課程包含下列各節:

必要條件

本教學課程需要 Visual Studio 2012 或 Visual Studio 2010。 如果使用 Visual Studio 2010,專案將會使用 .NET Framework 4,而不是.NET Framework 4.5。

如果您使用 Visual Studio 2012,建議您安裝ASP.NET 和 Web 工具 2012.2 更新。 此更新包含新功能,例如發佈、新功能和新範本的增強功能。

如果您有 Visual Studio 2010,請確定已安裝 NuGet

建立專案

在本節中,我們會在 Visual Studio 中建立專案。

  1. 從 [ 檔案] 功能表中,按一下 [ 新增專案]。

  2. 在 [新增專案]對話方塊中,展開 [範本] 底下的[C#],然後選取[Web]。

  3. 選取 [ASP.NET 空白 Web 應用程式 範本],將專案命名為 MoveShapeDemo,然後按一下 [ 確定]。

    建立新專案

新增 SignalR 和 JQuery.UI NuGet 套件

您可以藉由安裝 NuGet 套件,將 SignalR 功能新增至專案。 本教學課程也會使用 JQuery.UI 套件來允許拖曳和動畫顯示圖形。

  1. 按一下 [工具] |NuGet 套件管理員 |套件管理員主控台

  2. 在套件管理員中輸入下列命令。

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    SignalR 套件會將一些其他 NuGet 套件安裝為相依性。 安裝完成時,您擁有在 ASP.NET 應用程式中使用 SignalR 所需的所有伺服器和用戶端元件。

  3. 在套件管理員主控台中輸入下列命令,以安裝 JQuery 和 JQuery.UI 套件。

    Install-Package jQuery.ui.combined
    

建立基底應用程式

在本節中,我們將建立瀏覽器應用程式,在每次滑鼠移動事件期間,將圖形的位置傳送到伺服器。 伺服器接著會在收到此資訊時,將此資訊廣播給所有其他已連線的用戶端。 我們將在稍後的章節中展開此應用程式。

  1. 方案總管中,以滑鼠右鍵按一下專案,然後選取 [新增]、[類別...]。將類別命名為MoveShapeHub,然後按一下 [新增]。

  2. 以下列程式碼取代新 MoveShapeHub 類別中的程式碼。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    

    上述 MoveShapeHub 類別是 SignalR 中樞的實作。 如同使用 SignalR 教學課程消費者入門,中樞有用戶端直接呼叫的方法。 在此情況下,用戶端會將包含圖形之新 X 和 Y 座標的物件傳送至伺服器,然後廣播至所有其他連線用戶端。 SignalR 會使用 JSON 自動序列化此物件。

    將傳送至用戶端 (的物件) ShapeModel 包含要儲存圖形位置的成員。 伺服器上的 物件版本也包含一個成員,用來追蹤要儲存哪些用戶端的資料,讓指定的用戶端不會傳送自己的資料。 此成員會 JsonIgnore 使用 屬性,使其無法序列化並傳送至用戶端。

  3. 接下來,我們會在應用程式啟動時設定中樞。 在方案總管中,以滑鼠右鍵按一下專案,然後按一下 [新增] |全域應用程式類別。 接受 [ 全域 ] 的預設名稱,然後按一下 [ 確定]。

    新增全域應用程式類別

  4. 在 Global.asax.cs 類別中提供的using語句之後新增下列 using 語句。

    using System.Web.Routing;
    
  5. 在 Global 類別的 方法中 Application_Start 新增下列程式程式碼,以註冊 SignalR 的預設路由。

    RouteTable.Routes.MapHubs();
    

    您的 global.asax 檔案看起來應該如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. 接下來,我們將新增用戶端。 在方案總管中,以滑鼠右鍵按一下專案,然後按一下 [新增] |新增專案。 在 [ 新增專案] 對話方塊中,選取 [Html 頁面]。 為頁面提供適當的名稱 (,例如 Default.html) ,然後按一下 [ 新增]。

  7. 方案總管中,以滑鼠右鍵按一下您剛才建立的頁面,然後按一下 [設定為起始頁]。

  8. 以下列程式碼片段取代 HTML 頁面中的預設程式碼。

    注意

    確認下列腳本參考符合 [腳本] 資料夾中新增至專案的套件。 在 Visual Studio 2010 中,新增至專案的 JQuery 和 SignalR 版本可能不符合下列版本號碼。

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
    
                shapeModel = {
                    left: 0,
                    top: 0
                };
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    上述 HTML 和 JavaScript 程式碼會建立名為 Shape 的紅色 Div、使用 jQuery 程式庫啟用圖形的拖曳行為,並使用圖形的 事件將圖形 drag 的位置傳送至伺服器。

  9. 按 F5 啟動應用程式。 複製頁面的 URL,然後將它貼到第二個瀏覽器視窗中。 將圖形拖曳至其中一個瀏覽器視窗;其他瀏覽器視窗中的圖形應該移動。

    此螢幕擷取畫面顯示您在某個瀏覽器視窗中拖曳的圖形在另一個視窗中移動的方式。

新增用戶端迴圈

由於傳送每個滑鼠移動事件上圖形的位置將會建立不必要的網路流量,因此用戶端的訊息必須受到節流。 我們將使用 javascript setInterval 函式來設定迴圈,以固定速率將新位置資訊傳送至伺服器。 這個迴圈是「遊戲迴圈」的非常基本標記法,這是重複呼叫的函式,可驅動遊戲或其他模擬的所有功能。

  1. 更新 HTML 頁面中的用戶端程式代碼,以符合下列程式碼片段。

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    上述更新會新增 updateServerModel 函式,其會以固定頻率呼叫。 每當旗標指出有新的位置資料要傳送時, moved 此函式就會將位置資料傳送至伺服器。

  2. 按 F5 啟動應用程式。 複製頁面的 URL,並將其貼到第二個瀏覽器視窗中。 將圖形拖曳到其中一個瀏覽器視窗中;其他瀏覽器視窗中的圖形應該會移動。 由於傳送至伺服器的訊息數目將會受到節流控制,因此動畫不會像上一節一樣順暢。

    此螢幕擷取畫面顯示當您新增用戶端迴圈時,如何在某個瀏覽器視窗中拖曳圖形在另一個視窗中移動。

新增伺服器迴圈

在目前的應用程式中,從伺服器傳送至用戶端的訊息會如收到一樣頻繁地傳出。 這會顯示與用戶端上所見類似的問題;訊息的傳送頻率可能會比所需還要頻繁,而且連線可能會因為結果而遭到大量攻擊。 本節說明如何補救伺服器以實作計時器,以節流傳出訊息的速率。

  1. 將 的內容 MoveShapeHub.cs 取代為下列程式碼片段。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
    
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    
                _model = new ShapeModel();
                _modelUpdated = false;
    
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
    
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
    
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
    
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
        
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
    
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
    
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
    
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    

    上述程式碼會展開用戶端以新增 Broadcaster 類別,以使用 Timer .NET Framework 中的 類別節流傳出訊息。

    由於中樞本身是暫時性 (每次需要) 時建立中樞本身, Broadcaster 因此 會建立為單一。 .NET) 4 中引進的延遲初始化 (會用來延遲建立,直到需要為止,確保第一個中樞實例會在計時器啟動之前完全建立。

    接著,用戶端函式的 UpdateShape 呼叫會移出中樞的 UpdateModel 方法,以便在收到傳入訊息時,不再立即呼叫。 相反地,用戶端的訊息會以每秒 25 次呼叫的速率傳送,由 _broadcastLoop 類別內的 Broadcaster 計時器所管理。

    最後,類別不需要直接從中樞呼叫用戶端方法, Broadcaster 而是需要使用 取得目前作業中樞的參考 (_hubContext) GlobalHost

  2. 按 F5 啟動應用程式。 複製頁面的 URL,並將其貼到第二個瀏覽器視窗中。 將圖形拖曳到其中一個瀏覽器視窗中;其他瀏覽器視窗中的圖形應該會移動。 瀏覽器與上一節不會有可見的差異,但傳送至用戶端的訊息數目將會受到節流。

    此螢幕擷取畫面顯示當您新增伺服器迴圈時,如何在某個瀏覽器視窗中拖曳圖形在另一個視窗中移動。

在用戶端上新增平滑動畫

應用程式幾乎完全完成,但我們可以在用戶端上移動圖形的動作中,進一步改善,因為它會移動以回應伺服器訊息。 我們不會將圖形的位置設定為伺服器所提供的新位置,而是使用 JQuery UI 程式庫的 animate 函式,在圖形的目前與新位置之間順暢移動圖形。

  1. 更新用戶端的 updateShape 方法,看起來像下面醒目提示的程式碼:

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                 moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    上述程式碼會將圖形從舊位置移至伺服器在動畫間隔過程中所指定的新圖形 (,在此案例中為 100 毫秒) 。 在圖形上執行的任何先前動畫會在新的動畫開始之前清除。

  2. 按 F5 啟動應用程式。 複製頁面的 URL,並將其貼到第二個瀏覽器視窗中。 將圖形拖曳到其中一個瀏覽器視窗中;其他瀏覽器視窗中的圖形應該會移動。 另一個視窗中圖形的移動看起來應該比較不敏感,因為其移動會隨著時間插入,而不是在每個傳入訊息中設定一次。

    此螢幕擷取畫面顯示當您在用戶端上新增平滑動畫時,如何在某個瀏覽器視窗中拖曳圖形在另一個視窗中移動。

進一步步驟

在本教學課程中,您已瞭解如何設計 SignalR 應用程式,以在用戶端與伺服器之間傳送高頻率訊息。 此通訊範例適用于開發線上遊戲和其他模擬,例如 使用 SignalR 建立的GameR 遊戲

您可以從 程式碼庫下載本教學課程中建立的完整應用程式。

若要深入瞭解 SignalR 開發概念,請流覽下列適用于 SignalR 原始程式碼和資源的網站: