教學課程:使用 SignalR 2 建立高頻率即時應用程式

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

您建立的應用程式會顯示使用者可以拖曳的圖形。 伺服器會更新所有已連接瀏覽器中圖形的位置,以使用計時更新來比對拖曳圖形的位置。

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

在本教學課程中,您:

  • 設定專案
  • 建立基底應用程式
  • 應用程式啟動時對應至中樞
  • 新增用戶端
  • 執行應用程式
  • 新增用戶端迴圈
  • 新增伺服器迴圈
  • 新增平滑動畫

警告

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

必要條件

設定專案

在本節中,您會在 Visual Studio 2017 中建立專案。

本節說明如何使用 Visual Studio 2017 建立空白的 ASP.NET Web 應用程式,並新增 SignalR 和 jQuery.UI 程式庫。

  1. 在 Visual Studio 中,建立 ASP.NET Web 應用程式。

    建立 Web

  2. 在 [ 新增 ASP.NET Web 應用程式 - MoveShapeDemo ] 視窗中,保留 [ 空白 ] 並選取 [ 確定]。

  3. 方案總管中,以滑鼠右鍵按一下專案,然後選取 [新增>專案]。

  4. [新增專案 - MoveShapeDemo] 中,選取[已安裝>的 Visual C#>Web>SignalR],然後選取 [SignalR 中樞類別] (v2)

  5. 將類別命名為 MoveShapeHub ,並將其新增至專案。

    此步驟會建立 MoveShapeHub.cs 類別檔案。 同時,它會將一組支援 SignalR 的腳本檔案和元件參考新增至專案。

  6. 選取 [工具]>[NuGet 套件管理員]>[套件管理員主控台]。

  7. 套件管理員主控台中,執行此命令:

    Install-Package jQuery.UI.Combined
    

    此命令會安裝 jQuery UI 程式庫。 您可以使用它來建立圖形的動畫效果。

  8. 方案總管中,展開 [腳本] 節點。

    腳本程式庫參考

    專案會顯示 jQuery、jQueryUI 和 SignalR 的腳本程式庫。

建立基底應用程式

在本節中,您會建立瀏覽器應用程式。 應用程式會在每次滑鼠移動事件期間,將圖形的位置傳送至伺服器。 伺服器會即時將這項資訊廣播給所有其他連線的用戶端。 您可以在稍後的章節中深入瞭解此應用程式。

  1. 開啟 MoveShapeHub.cs 檔案。

  2. 以下列程式碼取代 MoveShapeHub.cs 檔案中的程式碼:

    using Microsoft.AspNet.SignalR;
    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; }
        }
    }
    
  3. 儲存檔案。

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

應用程式會將 ShapeModel 物件傳送至用戶端。 它具有成員來儲存圖形的位置。 伺服器上的 物件版本也有一個成員,可追蹤要儲存哪些用戶端的資料。 此物件可防止伺服器將用戶端的資料傳回本身。 此成員會 JsonIgnore 使用 屬性,讓應用程式無法序列化資料,並將它傳回用戶端。

應用程式啟動時對應至中樞

接下來,您會在應用程式啟動時設定與中樞的對應。 在 SignalR 2 中,新增 OWIN 啟動類別會建立對應。

  1. 方案總管中,以滑鼠右鍵按一下專案,然後選取 [新增>專案]。

  2. [新增專案 - MoveShapeDemo]中,選取[已安裝>的 Visual C#>Web],然後選取[OWIN 啟動類別]。

  3. 將類別命名為 Startup ,然後選取 [ 確定]。

  4. 以下列程式碼取代 Startup.cs 檔案中的預設程式碼:

    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(MoveShapeDemo.Startup))]
    namespace MoveShapeDemo
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
        }
    }
    

當應用程式執行 Configuration 方法時,OWIN 啟動類別會呼叫 MapSignalR 。 應用程式會使用 OwinStartup 元件屬性,將 類別新增至 OWIN 的啟動程式。

新增用戶端

新增用戶端的 HTML 頁面。

  1. 方案總管中,以滑鼠右鍵按一下專案,然後選取 [新增>HTML 頁面]。

  2. 將頁面命名為 [預設值 ],然後選取 [ 確定]。

  3. 方案總管中,以滑鼠右鍵按一下Default.html,然後選取 [設定為起始頁]。

  4. 以下列程式碼取代 Default.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.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.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>
    
  5. 方案總管中,展開 [腳本]。

    jQuery 和 SignalR 的腳本程式庫會顯示在專案中。

    重要

    套件管理員會安裝較新版本的 SignalR 腳本。

  6. 更新程式碼區塊中的腳本參考,以對應至專案中腳本檔案的版本。

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

執行應用程式

您可以執行應用程式,使其正常運作。 當您將圖形拖曳到瀏覽器視窗周圍時,圖形也會在其他瀏覽器中移動。

  1. 在工具列中,開啟 [腳本偵 錯],然後選取 [播放] 按鈕,以偵錯模式執行應用程式。

    使用者開啟偵錯模式並選取 [播放] 的螢幕擷取畫面。

    瀏覽器視窗會以右上角的紅色圖形開啟。

  2. 複製頁面的 URL。

  3. 開啟另一個瀏覽器,並將 URL 貼到網址列中。

  4. 將圖形拖曳到其中一個瀏覽器視窗中。 其他瀏覽器視窗中的圖形如下。

雖然應用程式會使用此方法,但不是建議的程式設計模型。 傳送的訊息數目沒有上限。 如此一來,用戶端和伺服器就會因為訊息和效能降低而造成負荷過大。 此外,應用程式也會在用戶端上顯示脫離的動畫。 此 jerky 動畫會發生,因為圖形會立即由每個方法移動。 如果圖形順暢地移動至每個新位置,則比較好。 接下來,您將瞭解如何修正這些問題。

新增用戶端迴圈

傳送每個滑鼠移動事件上圖形的位置會建立不必要的網路流量量。 應用程式需要節流來自用戶端的訊息。

使用 javascript setInterval 函式來設定迴圈,以固定速率將新位置資訊傳送至伺服器。 此迴圈是「遊戲迴圈」的基本標記法。它是重複呼叫的函式,可驅動遊戲的所有功能。

  1. 以下列程式碼取代 Default.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.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.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. 選取播放按鈕以啟動應用程式

  3. 複製頁面的 URL。

  4. 開啟另一個瀏覽器,並將 URL 貼到網址列中。

  5. 將圖形拖曳到其中一個瀏覽器視窗中。 其他瀏覽器視窗中的圖形如下。

由於應用程式會節流傳送至伺服器的訊息數目,因此動畫一開始不會像平常一樣出現。

新增伺服器迴圈

在目前的應用程式中,從伺服器傳送至用戶端的訊息會一樣頻繁地傳出。 此網路流量會在用戶端上看到類似的問題。

應用程式可以比所需的訊息更頻繁地傳送訊息。 因此,連線可能會變成水流。 本節描述如何補救伺服器,以新增計時器,以節流傳出訊息的速率。

  1. 以下列程式碼取代 的內容 MoveShapeHub.cs

    using System;
    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; }
        }
        
    }
    
  2. 選取播放按鈕以啟動應用程式。

  3. 複製頁面的 URL。

  4. 開啟另一個瀏覽器,並將 URL 貼到網址列中。

  5. 將圖形拖曳到其中一個瀏覽器視窗中。

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

最好瞭解中樞本身是暫時性的。 每次需要時都會建立它。 因此,應用程式會將 Broadcaster 建立為單一。 它會使用延遲初始化來延遲 Broadcaster 建立,直到需要為止。 這可確保應用程式會在啟動計時器之前完全建立第一個中樞實例。

然後,用戶端函式的 UpdateShape 呼叫會移出中樞 UpdateModel 的 方法。 每當應用程式收到傳入訊息時,它就不再立即呼叫。 相反地,應用程式會以每秒 25 次呼叫的速率,將訊息傳送給用戶端。 進程是由計時器從 類別內 Broadcaster 管理 _broadcastLoop

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

新增平滑動畫

應用程式幾乎已完成,但我們可以再進行一項改進。 應用程式會移動用戶端上的圖形,以回應伺服器訊息。 使用 JQuery UI 程式庫的 animate 函式,而不是將圖形的位置設定為伺服器指定的新位置。 它可以在其目前與新位置之間順暢地移動圖形。

  1. 更新Default.html檔案中的用戶端 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.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.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>
    
  2. 選取播放按鈕以啟動應用程式。

  3. 複製頁面的 URL。

  4. 開啟另一個瀏覽器,並將 URL 貼到網址列中。

  5. 將圖形拖曳到其中一個瀏覽器視窗中。

另一個視窗中圖形的移動看起來會比較少。 應用程式會在一段時間內插補其移動,而不是在每個傳入訊息設定一次。

此程式碼會將圖形從舊位置移至新的位置。 伺服器會提供圖形在動畫間隔過程中的位置。 在此情況下,這是 100 毫秒。 應用程式會在新的動畫開始之前清除圖形上執行的任何先前動畫。

取得程式碼

下載已完成的專案

其他資源

您剛才學到的通訊架構對於開發線上遊戲和其他模擬很有用,例如 使用 SignalR 建立的 GameR 遊戲

如需 SignalR 的詳細資訊,請參閱下列資源:

後續步驟

在本教學課程中,您:

  • 設定專案
  • 建立基底應用程式
  • 應用程式啟動時對應至中樞
  • 已新增用戶端
  • 執行應用程式
  • 已新增用戶端迴圈
  • 已新增伺服器迴圈
  • 已新增平滑動畫

請前往下一篇文章,瞭解如何建立使用 ASP.NET SignalR 2 提供伺服器廣播功能的 Web 應用程式。