SignalR 實現線上人數與廣播訊息

使用SignalR 實現全域廣播,群組廣播,特定使用者廣播功能,客製化OnConnected與OnDisconnected事件

一直以來聽過SignalR好多次,但卻沒有時間好好好去了解它

直到過年前才懂這套工具在前端Web有多好用

如果你是前端開發者,若要開發出不按F5就有互動性的網頁,你絕對不能不去學如何使用SignalR

以Facebook的即時通知訊息來講,它是以非常即時的方式通知你所參與的主題有被回應,增加互動性

再以Booking的訂房網頁為例,若是有人同時在跟你看同一個飯店時,該網頁就會彈出這一類的訊息"線上有xx人在看這個房型,只剩幾間房"

這時你就會陷入你本來不需要訂,卻因為怕被搶走而"不小心"就訂房了的情況

在沒學會SignalR之前,我不知道這該怎麼做

但學會SignalR後,這樣的功能就算對一個初學者來說,要做到個五成像應當也不難

這就是SignalR的厲害之處, 且它能以瀏覽器尋找出最適合的通訊方式

Forever Frame、Long Polling、Server Sent Event、WebSocket

以下要介紹的就是如果利用SignalR 實現以下功能:

1. 線上人數(廣播)

2. 群組廣播,並設定其變數值

3. 特定ClientID廣播,並設定其變數值

因為類似的文章在網路上應當可以找到非常多,所以如果想知道完整的步驟

可以跳到最後面看參考資料跟著做即可

一開始先用nuget 安裝 SingalR 與 Angular (網頁顯示用),並建立Hub class

SignalR 啟動時會在Server端建立Hub,並在Client端建立 Hub proxy,讓它們進行即時溝通資料

首先要完成的是簡單的實行線上人數的廣播,我們希望在client連線時就把人數加1,失去連線要減掉

在hub class裡

 public class MyPowerHub : Hub
    {
        public static List<string> lstConnectionID = new List<string>();
        public override Task OnConnected()
        {
            lstConnectionID.Add(Context.ConnectionId);
            broadCastOnlineCount(lstConnectionID.Count);
            return base.OnConnected();
        }

        public override Task OnDisconnected(bool stop)
        {
            lstConnectionID.Remove(Context.ConnectionId);
            broadCastOnlineCount(lstConnectionID.Count);
            return base.OnDisconnected(stop);
        }

        public void broadCastOnlineCount(int count)
        {
            Clients.All.broadCastOnlineCount(count);
        }
    }

在Client端也要建立被Server Hub呼叫的Client function

SamplePower.js (在SignalR與網頁建立完成後,會取得一個ClientID,這個ID與Server onConnected的ID是一樣的)

(function () {
    var app = angular.module('chat-app', []);

    app.controller('ChatController', function ($scope) {
        // scope variables
        $scope.toClientID = '';
        $scope.count = 0;
        $scope.connectionJoinFinishFlag = false;
        $scope.MyPowerHub = null; // holds the reference to hub

        $scope.MyPowerHub = $.connection.myPowerHub; // initializes hub
        //set clientID
        $.connection.hub.start().done(function () {
            $scope.toClientID = $.connection.hub.id;
            $scope.$apply();
        });
      
        $scope.MyPowerHub.client.broadCastOnlineCount = function (count) {
            $scope.count = count
            $scope.$apply();
        };
        
    })
}());

在網頁端更是簡單 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body ng-app="chat-app">
    Hello this is my first singalR

    <div ng-controller="ChatController" id="divChat">
        <div>
            Online user: <span ng-bind="count"></span> <br />            
        </div>       
    </div>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery.signalR-2.2.1.js"></script>
    <script src="signalr/hubs"></script>
    <script src="Scripts/angular.js"></script>
    <script src="Scripts/samplePower.js"></script>
</body>
</html>

特別要注意的是這段   <script src="signalr/hubs"></script>,這段是SignalR會自動幫我們產生的,並不是一個實體檔案

在執行階段的話,是可以看到這個被產生出來的檔案喔

就這樣三兩、下,我們就實現了線上人數的功能了,而且網頁關掉,人數也會同步減少

 

再來就要來介紹比較複雜一點點的群組廣播跟特定ClientID廣播

我們在網頁上放了兩個textbox,一個是在初始化時放入我們自己的ClientID以及GroupID以便測試

不同的是GroupID得先按Button讓使用者確認他們要加入到哪一個Group,以達到註冊的動作

讓Server知道這個Group有哪些ClientID要加到到Group裡

所以我們修改Hub class檔案,並加入三個function

    public class MyPowerHub : Hub
    {
        public static List<string> lstConnectionID = new List<string>();
        public override Task OnConnected()
        {
            lstConnectionID.Add(Context.ConnectionId);
            broadCastOnlineCount(lstConnectionID.Count);
            return base.OnConnected();
        }

        public override Task OnDisconnected(bool stop)
        {
            lstConnectionID.Remove(Context.ConnectionId);
            broadCastOnlineCount(lstConnectionID.Count);
            return base.OnDisconnected(stop);
        }

        public void broadCastOnlineCount(int count)
        {
            Clients.All.broadCastOnlineCount(count);
        }
        public void SetPower(string powerFlag, string clientID)
        {
            if (string.IsNullOrEmpty(clientID))
                Clients.All.ResetPower(powerFlag);
            else
                Clients.Client(clientID).ResetPower(powerFlag);            
        }
        public void RegisterGroup(String GroupId)
        {            
            Groups.Add(Context.ConnectionId, GroupId);    
        }
        public void SetGroupPower(string powerFlag, string GroupId)
        {            
            Clients.Group(GroupId).ResetPower(powerFlag);
        }
       
    }

client端修改如下

(function () {
    var app = angular.module('chat-app', []);

    app.controller('ChatController', function ($scope) {
        // scope variables
        $scope.name = 'Guest'; // holds the user's name
        $scope.power = ''; // holds the new message
        $scope.toClientID = '';
        $scope.toGroupID = ''; 
        $scope.count = 0;
        $scope.connectionJoinFinishFlag = false;
        //$scope.messages = []; // collection of messages coming from server
        $scope.MyPowerHub = null; // holds the reference to hub

        $scope.MyPowerHub = $.connection.myPowerHub; // initializes hub
        //set clientID
        $.connection.hub.start().done(function () {
            $scope.toClientID = $.connection.hub.id;
            $scope.connectionJoinFinishFlag = true;
            $scope.$apply();
        });

        // register a client method on hub to be invoked by the server
        $scope.MyPowerHub.client.ResetPower = function (powerFlag) {
            $scope.power = powerFlag
            $scope.$apply();
        };

      
        $scope.MyPowerHub.client.broadCastOnlineCount = function (count) {
            $scope.count = count
            $scope.$apply();
        };
        

        $scope.setPower = function () {
            // sends a new message to the server
            //***事件首字必須是小寫
            if ($scope.toClientID != '')
                $scope.MyPowerHub.server.setPower($scope.power, $scope.toClientID);
            if ($scope.toGroupID != '')
                $scope.MyPowerHub.server.setGroupPower($scope.power, $scope.toGroupID);
        }

        $scope.joinGroup = function () {
            $scope.MyPowerHub.server.registerGroup($scope.toGroupID);
            $scope.connectionJoinFinishFlag = true;
        }
    })
}());

View的部份即加入三個textbox跟兩個button即可

<div ng-controller="ChatController" id="divChat">
        <div>
            Online user: <span ng-bind="count"></span> <br />            
            ClientID: <input type="text" style="width:300px" ng-model="toClientID" /><br />
            GroupID: <input type="text" style="width:300px" ng-model="toGroupID" /> 
            <input type="button" value="JOIN" disabled ng-disabled=" (connectionJoinFinishFlag) " ng-click="joinGroup()" /><br />         
            power: <input type="text" ng-model="power" /><br />
            <input type="button" value="Send" disabled ng-disabled="!(connectionJoinFinishFlag && (power.length > 0))"  ng-click="setPower()" />
        </div>       
    </div>

 

Demo 結果如下

最後我們可以用Chrome來看一下SignalR是幫我們用什麼樣的方式送資料,以及送了什麼樣的資料

如同我們一開始所講的,共有四種方式可以選擇 Forever Frame、Long Polling、Server Sent Event、WebSocket

在這裡SignalR會根據我們的環境選擇了使用Server Sent Event

 

參考資料

https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-getting-started-with-signalr