使用SignalR、AngularJS、WebApi、Redis實作跨站點的即時通知功能
目前的資訊世界越來越講求的就是使用者經驗,要做到一個好的使用者經驗網站,幾乎可以宣告 " F5,你已經死了... ",不管任何需求,即便是最基本的畫面資料更新,也不可能要求使用者按下F5來取得最新的資料。而現在要做到即時通知功能,在微軟這個強大的盟友幫助下,似乎也不需要費甚麼力氣,使用SignalR便能輕易實現。我學一個新的東西時,很喜歡用自問自答的方式來幫助自己了解架構,為了讓印象更深刻,就把它寫出來分享。
何謂SignalR?
為了要方便開發者簡單的實作出即時通知的功能,在以往的架構下都是client端發出request跟server端要求資料,所以使用者資料更新的頻率取決於client端對server端發出request的頻率,但在server端資料沒有異動時,這樣的request無疑是浪費成本;假設能反過來,當server端有資料異動時,就告知client端,那一切將會變得十分美好...
其中最常使用的範例,應該就是聊天室了,但只要有即時資料更新的需求,SignalR都會是一個很好的解決方案。
每個client端在連接到server時會產生一個獨一無二的connectionID,並且連線會一直維持住,不會像傳統client端request過來server端response就結束。而server端也可以根據需求來決定要對所有連結的client發送廣播還是針對特定的client來推送訊息。
SignalR基本的運作由下列幾個要素組成
- hub
- hub proxy
- connectionID
- hub lifetime
- hub poxy lifetime
- server function
- client function
- 如何選擇發送對象
何謂hub
hub是server端的C#程式,使用nuGet下載singalR的package之後,便可在新增檔案處看到hub class,我們可以定義自己的hub類別。hub主要的功能就是建立與client端溝通的平台,並且由它來負責client端連線的管理、群組的管理以及訊息的推送等等。
何謂hub proxy
顧名思義,hub proxy就是一個代理者,它是屬於client端的程式,由javaScript來撰寫,它的主要目的就是跟後端的hub來做溝通,而且很酷的是兩邊溝通的複雜邏輯都已經被微軟的SignalR package封裝起來了,因此我們只須了解如何把兩者建立關係,即可使用SignalR帶給我們的即時通知功能。
hub 和 hub proxy如何建立關聯
server端的hub class "ChatHub" 和前端的hub proxy "$.connection.chatHub" 是透過類別的名稱來做對應,唯一要注意的點就是hub proxy名稱小寫開頭既可。
何謂connectionID
每個使用者開啟一個視窗連結到我們的網站,就會產生一個connectionID,而server端的hub就是透過connectionID來管理推送訊息 ,一般只要是有做登入功能的網站,使用者在登入之後,網站會以使用者的帳號來識別這個使用者,但connectionID和使用者帳號是不一樣的兩個東西,舉例子來講,假設使用者使用"AKEN1215"帳號登入會產生一組connectionID "GUID1";然後使用者用另開一個視窗再次登入系統,仍然是以"AKEN1215"帳號登入,那會產生另外一組connectionID"GUID2",此時對網站來說,這兩個頁面都是使用者"AKEN1215" ,但對signalR來說這兩個頁面是分別獨立的,因此假設只對"GUID1"推送訊息,則使用者只會在其中一個頁面收到訊息通知。因此,hub在server端做管理時可透過hub的生命週期所提供的事件,建立原本的會員帳號和connectionID之間的關係,才能確保功能一切正常。
何謂hub lifetime
server端的hub父類別所提供的生命週期事件
- onConnected:使用者連線建立成功時觸發,適合處理會員帳號和connectionID的關聯
- onDisconnected:使用者斷線時觸發
- onReconnected:使用者重新連線時觸發
何謂hub proxy lifetime
client端所提供可供利用的生命週期事件
- starting: 連線建立時觸發
- received:當有資料送達時觸發
- connectionSlow:連線變慢時觸發
- reconnecting:但連線中斷要重新建立連線時觸發
- reconnected:重新建立連線完畢時觸發
- stateChanged:狀態改變時觸發
- disconnected:段線時觸發
範例可參考下方
var connection = $.hubConnection();
connection.connectionSlow(function () {
console.log('We are currently experiencing difficulties with the connection.')
});
何謂Server function
定義在hub類別裡的方法,client端可以輕鬆透過hub proxy來呼叫server端所提供的方法,另外要注意的就是client端要呼叫hub所提供的方法,並不需要實體化hub類別,因為這一切,都會由SignalR本身處理hub的生命週期裡幫你做好。
呈上,因為hub物件的實體化是由SignalR所提供的機制在操作的,而SignalR不會一直保留hub物件,只有在client端有對hub操作的需求時,像是建立連線、呼叫server端方法時,才會由SignalR協助建立新的hub實體來處理client端需求,這代表著不可以利用hub class來做狀態的管理,簡單來講,就是要做狀態管理,必須透過生命週期所提供的事件,將資料寫入像是資料庫等等方式來管理狀態。
何謂Client function
client端透過hub proxy的參考所定義的client端方法
var proxy = $.connection.contosoChatHub;
proxy .client.addmessage= function () {
console.log('TEST');
};
第一行取得hub proxy的參考後,第二行透過proxy參考裡的client屬性定義自訂的方法"addmessage","addmessage"可以是任意名稱,主要是用作在server端hub呼叫前端方法時使用,最後,function的定義就是一般的javaScript寫法。
如何選擇發送對象?
上面大概描述了整個SignalR的架構,剩下最後一個要了解的議題便是要如何選擇想發送的對象,SignalR提供了多種方式來讓開發者使用,以下只列出常用的,基本上要發送訊息,下面的幾種選擇已經夠用了。
- Clients.All.addMsg => 發送全體
- Clients.Caller.addMsg =>哪個client端呼叫hub的,就發送給他
- Clients.Other.addMsg =>哪個client端呼叫hub的,除了他之外全都發
- Clients.Client(connectionID).addMsg=> 指定connectionID發送
- Clients.AllExcept(connectionID).addMsg =>除了指定的connectionID那人之外 ,全部發送
- Clients.Group( groupname).addMsg => 發送特定群組
- 剩下的可參考這
小結
SignalR可以大幅增加使用者體驗,client端和server端溝通複雜的部份,SignalR都幫我們包裝好了,因此不須費太大力氣就可以讓網站進化。但以上所述都僅只適用於同一站點,比如說A站點的使用者發送訊息給B站點的使用者,就必須再額外做一些機制來滿足,所以當基本架構了解後,下一步便是如何做到跨站點的即時通知,待續...
參考資料
ASP.NET SignalR Hubs API Guide - Server (C#)
ASP.NET SignalR Hubs API Guide - JavaScript Client