前言如果需要一個即時(Realtime)回應的網站,通常是利用前端JavaScript實作一個Timer重整頁面或是利用Ajax與Server端溝通拿資料,但是這樣會造成Server端不斷收到Client的請求,可能會使其他功能無法正常運作,Server的效能低落。
SignalR正是可以解決這個問題的技術,可以在Client開啟網頁後,Server與Client建立一條溝通的通道,於是可以當Server有資料需要更新時,主動發送通知訊息給Client,而不用Client不斷的與Server端做要求。
而在SignalR出現之前,已經有許多相關的技術,如Long-Polling、HTML5 WebSocket,而SignalR是屬於ASP.NET上的解決方案。
相關技術
一、Polling、Long-Polling 與 Streaming
《圖一》Ajax Polling、Long Polling及Streaming
在過去需要讓Client與Server溝通的方式,是利用 iframe 或XMLHttpRequest傳遞訊息的方法,這些技術產生與伺服器之間方便又接近及時的相關技巧,統稱為Comet Programming。
輪詢(polling)是讓Client定時送出HTTP 請求給Server獲取資料,,如果已經事先知道Server什麼時候會更新資料,那麼就可以使用這樣的方式讓Client端的資料同步更新,但是通常不會知道Server的資料何時會更新,會造成浪費網路資源的狀況。
長時間輪詢(long-polling)也是透過輪詢的機制,由Client對Server端發出請求後,如果有新的資料則立刻回傳給Client,如果Server沒有新的資料,會先等待一段時間,這段時間內有新資料則會回傳,若無等到逾時後則會傳回空結果。
雖然長時間輪詢可以減少產生原本輪詢造成網路頻寬浪費的狀況,但是如果在資料更新很頻繁的狀況下,長時間輪詢並不會比傳統的輪詢有效率,而且有時候資料量很大時,會造成連續的輪詢不斷產生,反而會讓效能更差。
串流(streaming)是讓伺服器在接收到瀏覽器所送出 HTTP 請求後,立即產生一個回應瀏覽器的連線,並且讓這個連線持續一段時間不要中斷,而伺服器在這段時間內如果有新的資料,就可以透過這個連線將資料馬上傳送給瀏覽器。
但由於串流是建立在 HTTP 協定上的一種傳輸機制,所以有可能會因為代理伺服器(proxy)或防火牆(firewall)將其中的資料存放在緩衝區中,造成資料回應上的延遲。
二、HTML5 WebSocketWebSocket 是定義在 HTML5 標準中的一個新的網頁傳輸方式,可在一條連線上提供全雙工、雙向的資料傳輸。WebSocket 使得用戶端和伺服器之間的資料交換變得更加簡單,允許Server端直接向Client端推播資料而不需要進行請求,在 WebSocket API 中,Client和Server只需要完成一次交握,兩者之間就直接可以建立永續性的連線,並允許資料進行雙向傳送。
瀏覽器與伺服器之間若要建立一條 WebSocket 連線,在一開始的交握階段中,要先從 HTTP 協定升級為 WebSocket 協定,範例如下:
Client端送出請求:

《圖二》
Server端回應:

《圖三》
而在不同程式語言中也有不同可以實作WebSocket的Library,如NodeJS的Socket.io,而在ASP.NET則可以使用SignalR。
三、實作SignalR本文將實作兩個利用SignalR技術的應用,第一個為即時性協作平台,第二是模擬Server通知須更新資料後,推送新的資料給前端,即時更新圖表內容。
- 安裝環境
新增一個空白的ASP.NET WEB應用程式專案:

《圖四》
選擇工具>NuGet封裝管理員>管理方案的NuGet套件

《圖五》
搜尋SignalR及安裝

《圖六》
如此一來,就能在ASP.NET專案中開發SignalR了!
- 實作即時性協作平台
透過SignalR可以實作一個類似Google Docs的協作平台,並可以記錄是誰編輯的,本文將實作出一個簡單範例供參考。
在專案上按右鍵並選擇新增項目

《圖七》
選擇Web>一般>OWIN啟動類別,命名為Startup.cs

《圖八》
程式碼自動產生後,於Configuration裡增加一行程式碼:
app.MapSignalR();

《圖九》
這一段程式主要的作用就是跟 MVC Router 一樣讓別人知道 SignalR 的位置。
選擇Web>SignalR>SignalR Hub類別,命名為SyscomHub.cs

《圖十》
SignalR 主要藉由”Hub”這個核心類別,為伺服器端以及瀏覽器搭建出溝通的管道。
而撰寫程式前,我們設計一下系統會需要處理的功能:
1.記錄使用者資訊
2.顯示現在線上的使用者
3.資料編輯區域
4.記錄最後編輯的使用者
記錄使用者及文件資訊,透過定義兩個類別來完成:

《圖十一》
UserData類別說明:
1.Sid:儲存SignalR自動產生的唯一識別碼
2.Name:儲存使用者於連線後自訂的名字
3.ConnectedTime:記錄使用者於何時連線的時間

《圖十二》
DocData類別說明:
1.lastEditId:記錄最後編輯使用者的Id
2.lastEditTime:記錄最後編輯的時間
3.lastEditName:記錄最後編輯使用者的名字
4.content:文件的內容
而上線清單利用Dictionary來儲存Id及使用者名稱,及在全域暫存文件的內容:

《圖十三》
接下來是實作Server端的Function提供Client呼叫,首先先定義使用者在連線上來後,需記錄使用者Id及名稱及加入上線清單內供之後的功能查詢,以及更新之前使用者所編輯的文件內容:

《圖十四》
因為需要使用者在開啟頁面時輸入名字並傳到Signal Hub上,因此自訂了一個onJoinRoom Function並透過JSON格式傳使用者的名稱提供Client端呼叫,Function內會抓取使用者連線上來的時間,並將使用者資訊加入剛剛定義的上線清單Dictionary裡;第三行是呼叫Client Code的意思,而Server端可以透過幾種不同的方式,決定對哪些Client做呼叫:
Clients.All.function():呼叫所有使用者
Clients.Others.function():呼叫除了自己的使用者
Clients.AllExcept(Context.ConnectionId).function():呼叫除了某個ID外的使用者
Clients.Caller.function():呼叫自己
Clients.Client(Context.ConnectionId).function():呼叫特定使用者
所以在onJoinRoom時,將自己加入上線清單後,再通知其他Client更新上線清單,就可以知道現在在線上的使用者有哪些人。
在離線時也是相同的方式,而離線可以複寫Hub的OnDisconnected Function如下:

《圖十五》
當使用者離線後會呼叫此Function,會將使用者從上線清單移除,並通知其他使用者更新上線清單。
最後是當使用者編輯文件時,會記錄更新後的文件內容,及編輯的使用者資訊及時間:

《圖十六》
此Function定義提供Client端更新文件時,呼叫此Server Code,會更新使用者編輯時間以及是哪位使用者編輯,最後通知所有使用者更新文件內容。
第二部分是Client Code,首先要引入幾個Javascript檔:

《圖十七》
其中/signalr/hubs是動態生成的位置,編譯後才會產生。
根據前面設計的區塊,畫面會有以下幾個元件組成:

《圖十八》
online-user-list:上線人員清單
last-edit-time:上次編輯時間
last-edit-user:上次編輯的使用者
edit-doc-form:編輯文件區域
程式在一開始會要求使用者輸入姓名,這裡先用預設的跳出輸入視窗實作:

《圖十九》

《圖二十》
建立與Server端Hub的物件,Hub名稱為在Server Hub裡定義的Class名稱,名稱開頭需要小寫。

《圖二十一》
$.connection.hub.start()才真正將連線打開,如果成功連上Hub會進到.done()的部分,而此時需要將使用者加入到上線清單,於是呼叫server code上定義的onJoinRoom Function,並將使用者輸入的名字透過JSON Object傳到Server端。
接下來定義Client端被Server端呼叫的Function如下:

《圖二十二》
此為Client code的getOnlineList function,Server端呼叫時會將上線清單的Dictionary物件傳至前端,透過遍歷物件的方式,將使用者清單列在畫面上。

《圖二十三》
更新文件內容的function,會將畫面中編輯文件區的內容更新到最新的資料。

《圖二十四》
而編輯時,需要偵測使用者是否有更改輸入的動作,若有則將更新後文件內容及編輯使用者資訊傳至Server並通知其他使用者更新。
以下是範例的展示:
首先會先要求使用者輸入使用者名稱:

《圖二十五》

《圖二十六》
輸入後再上線清單可以看到剛剛輸入的名字,此時再多開一個瀏覽器視窗,模擬多個使用者:

《圖二十七》

《圖二十八》
就可以同時看到目前所有在線上的使用者清單,然後試試在輸入框中輸入文字,畫面上就會更新文件的內容:

《圖二十九》
以上就是第一個範例的展示,這個範例還有很多改進的空間,例如可以透過CSS美化如Bootstrap等CSS Framework,以及連接資料庫讓內容可以儲存起來以及管理使用者登入登出的功能。
- 實作即時更新圖表資料及重繪
有許多系統有即時監控數據圖表的需求,如股票或是系統的硬體資料,這個範例將模擬系統定期更新資料,並將Client的圖表更新,本範例利用C3.JS作為圖表繪圖的函式庫。
Hub的功能實作,可以在使用者連線時啟動一個Trigger或是Timer,如資料更新時,則會呼叫Client端的更新function,將圖表的資料重新繪製。
而Client Code本範例已監控系統硬體CPU、Memory、Disk為例,畫面元件如下:

《圖三十》
C3.JS的使用方式如下:

《圖三十一》
Data為須呈現於圖表上的欄位及值,bindto會將此圖表綁定至已定義好的網頁元件上。
圖表的更新方式如下:

《圖三十二》
透過Server呼叫Client的updateData function,用原先定義的chart.load(json:data)將資料更新。
範例的展示如下:
初始畫面:

《圖三十三》
更新資料後的畫面:

《圖三十四》
結論透過SignalR可以即時的讓Client與Server快速溝通,除了在PC上瀏覽器端,也可以實作在手機上,達到遠端遙控的功能,如開發一個簡報器的網頁App,則可以控制同樣是以SignalR溝通的簡報投影片。
參考資料1.
SignalR.net2.
WebSocket3.
WebSocket 通訊協定簡介:比較 Polling、Long-Polling 與 Streaming 的運作原理4.
C3.JS