共用方式為


教學課程:使用 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. 將圖形拖曳到其中一個瀏覽器視窗中。 其他瀏覽器視窗中的圖形會跟在後面。

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

新增客戶端迴圈

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

使用 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 次呼叫的速率,將訊息傳送給用戶端。 進程是由 _broadcastLoop 類別內的 Broadcaster 定時器所管理。

最後,類別必須取得目前作業_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 的詳細資訊,請參閱下列資源:

下一步

在本教學課程中,您已:

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

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