本系列會分爲2-3篇文章.javascript
本文的內容:html
SignalR是一個.NET Core/.NET Framework的開源實時框架. SignalR的可以使用Web Socket, Server Sent Events 和 Long Polling做爲底層傳輸方式.java
SignalR基於這三種技術構建, 抽象於它們之上, 它讓你更好的關注業務問題而不是底層傳輸技術問題.web
SignalR這個框架分服務器端和客戶端, 服務器端支持ASP.NET Core 和 ASP.NET; 而客戶端除了支持瀏覽器裏的javascript之外, 也支持其它類型的客戶端, 例如桌面應用.npm
SignalR使用的三種底層傳輸技術分別是Web Socket, Server Sent Events 和 Long Polling.瀏覽器
其中Web Socket僅支持比較現代的瀏覽器, Web服務器也不能太老.服務器
而Server Sent Events 狀況可能好一點, 可是也存在一樣的問題.websocket
因此SignalR採用了回落機制, SignalR有能力去協商支持的傳輸類型.app
Web Socket是最好的最有效的傳輸方式, 若是瀏覽器或Web服務器不支持它的話, 就會降級使用SSE, 實在不行就用Long Polling.負載均衡
一旦創建鏈接, SignalR就會開始發送keep alive消息, 來檢查鏈接是否還正常. 若是有問題, 就會拋出異常.
由於SignalR是抽象於三種傳輸方式的上層, 因此不管底層採用的哪一種方式, SignalR的用法都是同樣的.
SignalR默認採用這種回落機制來進行傳輸和鏈接.
可是也能夠禁用回落機制, 只採用其中一種傳輸方式.
RPC (Remote Procedure Call). 它的優勢就是能夠像調用本地方法同樣調用遠程服務.
SignalR採用RPC範式來進行客戶端與服務器端之間的通訊.
SignalR利用底層傳輸來讓服務器能夠調用客戶端的方法, 反之亦然, 這些方法能夠帶參數, 參數也能夠是複雜對象, SignalR負責序列化和反序列化.
Hub是SignalR的一個組件, 它運行在ASP.NET Core應用裏. 因此它是服務器端的一個類.
Hub使用RPC接受從客戶端發來的消息, 也能把消息發送給客戶端. 因此它就是一個通訊用的Hub.
在ASP.NET Core裏, 本身建立的Hub類須要繼承於基類Hub.
在Hub類裏面, 咱們就能夠調用全部客戶端上的方法了. 一樣客戶端也能夠調用Hub類裏的方法.
這種Hub+RPC的方式仍是很是適合實時場景的.
以前說過方法調用的時候能夠傳遞複雜參數, SignalR能夠將參數序列化和反序列化. 這些參數被序列化的格式叫作Hub 協議, 因此Hub協議就是一種用來序列化和反序列化的格式.
Hub協議的默認協議是JSON, 還支持另一個協議是MessagePack. MessagePack是二進制格式的, 它比JSON更緊湊, 並且處理起來更簡單快速, 由於它是二進制的.
此外, SignalR也能夠擴展使用其它協議..
隨着系統的運行, 有時您可能須要進行橫向擴展. 就是應用運行在多個服務器上.
這時負載均衡器會保證每一個進來的請求按照必定的邏輯分配到多是不一樣的服務器上.
在使用Web Socket的時候, 沒什麼問題, 由於一旦Web Socket的鏈接創建, 就像在瀏覽器和那個服務器之間打開了隧道同樣, 服務器是不會切換的.
可是若是使用Long Polling, 就可能有問題了, 由於使用Long Polling的狀況下, 每次發送消息都是不一樣的請求, 而每次請求可能會到達不一樣的服務器. 不一樣的服務器可能不知道前一個服務器通訊的內容, 這就會形成問題.
針對這個問題, 咱們須要使用Sticky Sessions (粘性會話).
Sticky Sessions 貌似有不少中實現方式, 可是主要是下面要介紹的這種方式.
做爲第一次請求的響應的一部分, 負載均衡器會在瀏覽器裏面設置一個Cookie, 來表示使用過這個服務器. 在後續的請求裏, 負載均衡器讀取Cookie, 而後把請求分配給同一個服務器.
使用空模板創建ASP.NET Core項目.
創建一個CountService:
創建一個CountHub, 繼承於Hub:
在Startup裏註冊SignalR:
若是須要的話能夠在AddSignalR()這個方法裏使用lambda表達式進行一些配置.
而後在管道里使用SignalR, 使用app.UseSignalR():
這裏我已經創建了一個Hub, 叫作CountHub.
該方法的參數類型是Action<HubRouteBuilder>, 而後在這裏配置hub的路由.
首先創建一個Controller, 並注入IHubContext<CountHub>:
接下來咱們就可使用IHubContext<CountHub>這個對象與客戶端進行實時通訊了.
下面創建一個POST Action, 客戶端點擊按鈕以後來到這個Action, 在這裏咱們使用hub爲全部的客戶端發送一個消息:
這裏, 我調用了全部客戶端上的someFunc這個方法, 參數是一個對象.
可是使用這種IHubContext<Hub>的注入方式, 咱們沒法在它那取得Caller(調用該方法的客戶端)這個屬性.
從Hub的Context屬性, 咱們能夠得到用戶的信息.
咱們在CountHub裏override父類的一個方法OnConnectedAsync():
若是有新的鏈接創建了, 這個方法就會被執行.
在Hub類裏, 咱們能夠訪問到Context屬性. 從Context屬性那, 咱們能夠得到一個經常使用的屬性叫作ConnectionId. 這個ConnectionId就是鏈接到Hub的這個客戶端的惟一標識.
使用ConnectionId, 咱們就能夠取得這個客戶端, 並調用其方法, 如圖中的Clients.Client(connectionId).xxx.
Hub的Clients屬性表示客戶端, 它有若干個方法能夠選擇客戶端, 剛纔的Client(connectionId)就是使用connectionId找到這一個客戶端. 而AllExcept(connectionId)就是除了這個connectionId的客戶端以外的全部客戶端. 更多方法請查看文檔.
SignalR還有Group分組的概念, 並且操做簡單, 這裏用到的是Hub的Groups屬性. 向一個Group名添加第一個connectionId的時候, 分組就被創建. 移除分組內最後一個客戶端的時候, 分組就被刪除了. 使用Clients.Group("組名")能夠調用組內客戶端的方法.
SignalR會採用ASP.NET Core配置好的受權和驗證體系.
用法和Controller差很少:
想要取得User對象, 須要使用Context.User, 它的類型是ClaimsPrinciple:
客戶端須要安裝signalr這個庫. 可使用npm安裝 @aspnet/signalr
可是實際上只須要signalr.js一個文件便可.
客戶端代碼以下:
點擊按鈕後先執行Controller的POST方法, POST返回的是Accepted(1), 因此id是1.
使用singalR對象的HubConnectionBuilder來構建connection. 使用返回的connection對象, 咱們能夠用它的on方法來處理服務器端方法調用的響應. 響應方法的參數能夠是簡單類型也能夠是複雜的對象.
使用connection.start()來打開鏈接, 使用catch()來捕獲異常, 使用connection.stop() 關閉鏈接.
先運行一下看看效果:
能夠看到使用Clients.All, 全部的客戶端的方法都會被調用.
剛打開頁面的時候, 咱們就嘗試創建鏈接, 從F12能夠看到一個叫作negotiate的請求被髮送了:
這個請求的body以下:
能夠看到客戶端選擇了一個connectionId, 裏面還有瀏覽器支持的傳輸方式.
服務器的響應:
響應也包含着connectionId, 以及服務器支持的傳輸方式. 這裏三種都支持. 因爲我沒有指定傳輸方式, 因此SignalR選擇了最好的方式: websocket.
而在我點擊按鈕後, Web Socket鏈接才被初始化:
若是須要手動指定傳輸方式, 請在withUrl()方法的第二個參數指定傳輸方式:
.NET 客戶端能夠安裝 Microsoft.AspNetCore.SignalR.Client 這個包來支持SignalR.
具體用法請查看官方文檔, 語法和js的差很少.
須要安裝 Microsoft.AspNetCore.SignalR.Protocols.MessagePack.
而後在Startup裏面使用AddMessagePackProtocol()這個方法便可:
這樣的話, 服務器端既支持JSON, 也支持MessagePack了.
另外.NET客戶端也須要安裝這個MessagePack包.
而js客戶端須要安裝 @aspnet/signalr-protocol-msgpack.
能夠採用Redis, 須要安裝 Microsoft.AspNetCore.SignalR.Redis. 這個包.
而後在Startup裏面配置:
這個沒試過, 請看官方文檔.
SignalR就介紹這些....