使用SignalR實現即時通訊功能


教程簡介

SignalR的好處是可以讓多個客戶端之間進行互動,比如這篇教程就展示了當你在頁面上拖動矩形方塊的同時,其它打開這個頁面的用戶也將會看到你拖動的軌跡以及最終的結果,當然他們也可以通過拖動該方塊來改變你的網頁上該方塊的位置。如果對其進行擴展一下,說不定還能搞出來一個供多人完成的塗鴉牆。該教程出自High-Frequency Realtime with SignalR 2,有興趣的可以看原教程。

運行結果

創建項目

1.打開Visual Studio,並創建一個新的ASP.NET Web Application項目,選擇Empty模板,然后點擊OK,創建項目。

2.依次單擊Tools | NuGet Package Manager | Package Manager Console,打開NuGet控制台。

3.輸入命令Install-Package Microsoft.AspNet.SignalR安裝SignalR組件。

4.輸入命令Install-Package jQuery.UI.Combined安裝jQuery UI,並升級jQuery文件。

5.打開Solution Explorer可以看到對應的jQueryjQueryUISignalR,如下圖所示。

引用的組件

添加Hub

1.右擊項目文件,依次點擊Add | New Item | Web | SignalR | SignalR Hub Class(v2),然后創建一個名為MoveShapeHub.cs的Hub文件。

2.在MoveShapeHub類中輸入下面這段代碼。

using Newtonsoft.Json;
using Microsoft.AspNet.SignalR;

namespace MoveShapeDemo
{
    public class MoveShapeHub : Hub
    {
        public void UpdateModel(ShapeModel clientModel)
        {
            clientModel.LastUpdateBy = Context.ConnectionId;

            // Clients.Others.updateShape(clientModel);
            Clients.AllExcept(clientModel.LastUpdateBy).updateShape(clientModel);
        }
    }

    public class ShapeModel
    {
        /// <summary>
        /// 使用JsonProperty指定的名稱來序列化該屬性
        /// </summary>
        [JsonProperty("left")]
        public double Left { get; set; }

        /// <summary>
        /// 同上
        /// </summary>
        [JsonProperty("top")]
        public double Top { get; set; }

        /// <summary>
        /// 使用JsonIgnore表示序列化時忽略該屬性
        /// </summary>
        [JsonIgnore]
        public string LastUpdateBy { get; set; }
    }
}

添加Hub的啟動文件

1.使用SignalR必需要添加啟動文件(而這個啟動文件則是基於OWIN的),在Solution Explorer里,右鍵項目,然后依次點擊Add | New Item | Web | General | OWIN Startup Class,創建一個名為Startup.cs的啟動文件。

2.在Startup類中輸入下面這段代碼。

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MoveShapeDemo.Startup))]

namespace MoveShapeDemo
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

添加客戶端文件

1.服務端的工作完成后,再來編寫客戶端的代碼,右鍵項目,並在根目錄新建一個名為index.html的網頁文件。

2.將下面這段代碼拷備到新建的網頁文件中。

<!DOCTYPE html>
<html>
<head>
    <title>SignalR MoveShape Demo</title>
    <style>
        #shape {
            width: 100px;
            height: 100px;
            background-color: #FF0000;
        }
    </style>
    <script src="Scripts/jquery-1.12.4.js"></script>
    <script src="Scripts/jquery-ui-1.12.1.js"></script>
    <script src="Scripts/jquery.signalR-2.2.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>
</head>
<body>
    <div id="shape" />
</body>
</html>

3.准備注緒后,按F5,並使用多個瀏覽器打開,試着在任何一個瀏覽器頁面上拖動這個方塊,就可以看到不管你在哪一個瀏覽器頁面托動這個方塊,所有打開的頁面上這個方塊都會跟着移動。不過這個測試案例並不是很好,因為只要你移動方塊,它就會與服務器進行交互,這樣就會造成網絡帶寬不必要的浪費,所以要改進一下。

改進一:客戶端定時發送移動的新坐標

為了不使客戶端移動的每一像素都發送給服務器,我們決定采用setInterval函數來讓客戶端定時把最新的坐標信息發送給服務器,並且加上一個開關,只有在移動后才會發送最新的坐標數據。

將靜態頁的代碼改為下面這個樣子(只給出腳本部分)

<script>
    var moveShapeHub,
        shapeModel,
        moved;

    $(function () {
        moveShapeHub = $.connection.moveShapeHub;
        moved = false;
        shapeModel = {
            left: 0,
            top: 0
        };

        var $shape = $("#shape"),
            messageFrequency = 10,                  // 限定每秒最多發送10個消息
            updateRate = 1000 / messageFrequency;   // 設置每個消息發送的間隔時間

        // 聲明的回調方法
        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;
                }
            });

            // 使用定時器定時向服務器發送新坐標
            setInterval(updateServerModel, updateRate);
        });
    });

    function updateServerModel() {
        if (moved) {
            moveShapeHub.server.updateModel(shapeModel);
            moved = false;
        }
    }
</script>

重新運行程序,再次拖動矩形時,客戶端就會以每秒最多10次的頻率來與服務器傳遞數據,雖然節省了帶寬,不過其它客戶端的矩形在被移動時就會像PPT一樣,沒有那么順滑了,不過這都不叫事兒。

改進二:服務器定時將數據廣播給其它客戶端

在這個例子中,服務器端只要一接收到數據就將它廣播給其它的客戶端,而這就會存在與上面客戶端類似的問題,所以我們這次也要對服務器端的代碼進行改造,讓它定時向客戶端廣播數據。

MoveShapeHub.cs里的代碼改為下面的代碼。

using Microsoft.AspNet.SignalR;
using Newtonsoft.Json;
using System;
using System.Threading;

namespace MoveShapeDemo
{
    public class Broadcaster
    {
        /// <summary>
        /// 延時加載的功能
        /// </summary>
        private readonly static Lazy<Broadcaster> _instance = new Lazy<Broadcaster>(() => new Broadcaster());
        private readonly TimeSpan BroadcastInterval = TimeSpan.FromMilliseconds(40);
        private readonly IHubContext _hubContext;
        private Timer _broadcastLoop;
        private ShapeModel _model;
        private bool _modelUpdated;

        private Broadcaster()
        {
            // 獲得對指定Hub連接的引用
            _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
            _model = new ShapeModel();
            _modelUpdated = false;
            _broadcastLoop = new Timer(BroadcastShape, null, BroadcastInterval, BroadcastInterval);
        }

        public static Broadcaster Instance
        {
            get { return _instance.Value; }
        }

        /// <summary>
        /// 將最新坐標廣播給其它客戶端(被定時器循環調用)
        /// </summary>
        /// <param name="state"></param>
        private void BroadcastShape(object state)
        {
            if (_modelUpdated)
            {
                _hubContext.Clients.AllExcept(_model.LastUpdateBy).updateShape(_model);
                _modelUpdated = false;
            }
        }

        public void UpdateShape(ShapeModel clientModel)
        {
            _model = clientModel;
            _modelUpdated = true;
        }
    }

    /// <summary>
    /// Hub的生命周期比較短(只有在需要的時候才會創建)
    /// </summary>
    public class MoveShapeHub : Hub
    {
        private Broadcaster _broadcaster;

        public MoveShapeHub() :
            this(Broadcaster.Instance)
        { }

        public MoveShapeHub(Broadcaster broadcaster)
        {
            _broadcaster = broadcaster;
        }

        public void UpdateModel(ShapeModel clientModel)
        {
            clientModel.LastUpdateBy = Context.ConnectionId;

            // 將原來的廣播消息給其它客戶端的代碼交由Broadcaster來管理,
            // 這樣就能夠避免只要有數據傳入就立即廣播給其它客戶端的情況
            // 出現。取而代之的是使用Timer類的定時廣播功能,從而達到從
            // 服務器這端節省資源的目的。
            _broadcaster.UpdateShape(clientModel);
        }
    }

    public class ShapeModel
    {
        /// <summary>
        /// 使用JsonProperty指定的名稱來序列化該屬性
        /// </summary>
        [JsonProperty("left")]
        public double Left { get; set; }

        /// <summary>
        /// 同上
        /// </summary>
        [JsonProperty("top")]
        public double Top { get; set; }

        /// <summary>
        /// 使用JsonIgnore表示序列化時忽略該屬性
        /// </summary>
        [JsonIgnore]
        public string LastUpdateBy { get; set; }
    }

重新運行項目,其實運行效果和之前並沒有什么兩樣,只是服務器端不再會接到客戶端發來的數據后就立即廣播給其它客戶端,而是會做記錄,等到計器器下一次起動廣播時再把這個坐標廣播給其它客戶端,不過被廣播的用戶依然會有看PPT的感覺。

改進三:為客戶端添加動畫效果

現在我們還要做最后一點改動,那就是解決被移動的矩形看起來像PPT一樣的問題,只里要將客戶端聲明的回調方法(updateShape)做適當的改動即可,代碼如下所示。

// 聲明的回調方法
moveShapeHub.client.updateShape = function (model) {
	shapeModel = model;

    // 使用動畫效果使得矩形被移動時看起來更平滑,
	// duration:動畫的持續時間
	// queue:是否在效果隊列中放置動畫
	$shape.animate(shapeModel, { duration: updateRate, queue: false });
};


免責聲明!

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



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