歡迎光臨
每天分享高質量文章

NetCore + SignalR 實現日誌訊息推送

哈嘍大家週一好呀,感覺好久沒有寫文章了,上週出差了一次,感覺還是比坐辦公室好的多,平時在讀一本書《時生》,感興趣的可以看看?……

這幾天翻看 NetCore 相關知識擴充套件的時候,發現了久違的一個知識點 —— SignalR ,為啥說久違呢,因為去年的時候,我在公司的專案裡就想用了,後來組員說他學學看,也沒有了下文,我也就耽擱了,昨天突然看到這個了,想著正好看看吧,儘量落地到 NetCore 專案上,當時我很自信的以為這個技術很老了,應該用的人很多,可是天不遂人願,在.Net MVC中使用的很多有六成,在NetCore 的小demo有兩成,NetCore + Vue 一起使用的就是寥寥無幾了,而且更多的是仿照官網的,好吧,我就簡單寫一個吧,希望對大家有所幫助,儘量將這個技術落地。

你一定很好奇,為啥要學 SignalR ,或者說它有啥作用,那我先說幾個場景,你就知道了:

1、使用者登入處理相關場景; //平時我們是ajax請求,等待後端處理,然後傳回 true ,根據傳回結果相應處理;

2、後端(c#)強制使用者退出登入(js); //這個後端還真沒有做過,沒啥思路

3、使用者支付訂單,等待成功後跳轉; //我以前用的是 ajax 輪詢,額。。。

4、給使用者發訊息,或網頁內簡單的聊天;

5、秒殺使用者名稱單,在頁面實時進行滑動展示;

大家從這幾個慄子中,可以看到一個共性:

就是想用後端來操作前端,也就是說可以透過 服務端程式碼,來控制前端 js 事件,來實現響應式的實時場景過程,使用者完全不用做任何操作,或者做少量的操作就能實現更多的效果。

大家可以先自己用自己平時的想法和經驗來實現上邊的場景,當然並不是一定使用 SignalR ,Scoket 現在也是很火,本文就是簡單說說 SignalR 的基礎用法,想了想,怎麼才能讓這個技術落地,最後決定將全部的操作日誌透過 SignalR 的形式在Admin後臺展示出來吧,以後也試試強制登入的功能

1、基本概念

本文重點說如何使用,但是為了文章的完整性,還是貼上了一些概念講解,參考《ASP.NET Core SignalR 簡介》,更多的知識點請自行研究吧,網上這種概念類文章很多:

ASP.NET Core SignalR 是一個開原始碼庫,它簡化了嚮應用新增實時 Web 功能的過程。 實時 Web 功能使伺服器端程式碼能夠即時將內容推送到客戶端。

SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作為底層傳輸方式。

SignalR 的適用物件:

  • 需要來自伺服器的高頻率更新的應用。 例如:遊戲、社交網路、投票、拍賣、地圖和 GPS 應用。
  • 儀錶板和監視應用。 示例包括公司儀錶板、銷售狀態即時更新或行程警示。
  • 協作應用。 協作應用的示例包括白板應用和團隊會議軟體。
  • 需要通知的應用。 社交網路、電子郵件、聊天、遊戲、行程警示以及許多其他應用都使用通知。

SignalR 提供了一個用於建立伺服器到客戶端 《遠端過程呼叫(RPC》的 API。 RPC 透過伺服器端 .NET Core 程式碼呼叫客戶端上的 JavaScript 函式。

以下是 ASP.NET Core SignalR 的一些功能:

  • 自動管理連線。
  • 同時向所有連線的客戶端傳送訊息。 例如,聊天室。
  • 將訊息傳送到特定的客戶端或客戶端組。
  • 擴充套件以處理增加的流量。

為啥要使用它,因為它是微軟的。

2、支援平臺

服務端:ASP.NET Core SignalR 適用於 ASP.NET Core 支援的任何伺服器平臺。

JS 客戶端:需要支援NodeJS 8+、或者常見主流瀏覽器都支援。

Java 客戶端:支援Java 8或更高版本。

Net 客戶端:可以在 ASP.NET Core 支援的任何平臺上執行。 例如, Xamarin 開發人員可以使用 SignalR用於構建 Android 應用程式使用 Xamarin.Android 8.4.0.1 或更高版本和 iOS 應用程式使用 Xamarin.iOS 11.14.0.4 或更高版本。如果伺服器執行 IIS,Websocket 傳輸要求安裝 IIS 8.0 或更高版本在 Windows Server 2012 或更高版本。 其他傳輸在所有平臺上都受支援。

3、回落機制

參考文章《SignalR簡介及使用》

SignalR使用的三種底層傳輸技術分別是Web Socket, Server Sent Events 和 Long Polling;

其中Web Socket僅支援比較現代的瀏覽器,Web伺服器也不能太老;

而Server Sent Events 情況可能好一點, 但是也存在同樣的問題。

Web Socket是最好的最有效的傳輸方式, 如果瀏覽器或Web伺服器不支援它的話, 就會降級使用SSE, 實在不行就用Long Polling;

一旦建立連線, SignalR就會開始傳送keep alive訊息, 來檢查連線是否還正常, 如果有問題, 就會丟擲異常;

因為SignalR是抽象於三種傳輸方式的上層, 所以無論底層採用的哪種方式, SignalR的用法都是一樣的;

SignalR預設採用這種回落機制來進行傳輸和連線。但是也可以禁用回落機制, 只採用其中一種傳輸方式。

4、Hub元件

Hub是SignalR的一個元件, 它執行在ASP.NET Core應用裡. 所以它是伺服器端的一個類;

Hub使用RPC接受從客戶端發來的訊息, 也能把訊息傳送給客戶端, 所以它就是一個通訊用的Hub;

在ASP.NET Core裡, 自己建立的Hub類需要繼承於基類Hub;

在Hub類裡面, 我們就可以呼叫所有客戶端上的方法了, 同樣客戶端也可以呼叫Hub類裡的方法;

之前說過方法呼叫的時候可以傳遞複雜引數, SignalR可以將引數序列化和反序列化, 這些引數被序列化的格式叫做Hub 協議,所以Hub協議就是一種用來序列化和反序列化的格式;

Hub協議的預設協議是JSON, 還支援另外一個協議是MessagePack, MessagePack是二進位制格式的, 它比JSON更緊湊, 而且處理起來更簡單快速, 因為它是二進位制的;

此外, SignalR也可以擴充套件使用其它協議。

好啦,複雜而又枯燥的概念說完了,接下來咱們開始動手寫程式碼了!(額概念還是要看的?)

既然要實現實時互動,肯定得有服務端,那我們就直接在 Blog.Core 專案上,進行處理吧

1、取用 SignalR 包

為了以後好拓展,我就把 SignalR 中心,也可以是通訊管道定義到了 Common 層,當然可以自定義任意層。

//請看清,還有一個 Net 版本的,但是也能用,還是用 core 版本的吧
Install-Package Microsoft.AspNetCore.SignalR

2、宣告 Hub 管道——集線器

在 Blog.Core.Common 層,新建一個 Hubs 檔案夾,然後新增一個 ChatHub 類:

 public class ChatHub : Hub
 {
     public async Task SendMessage(string user, string message)
     {
         await Clients.All.SendAsync("ReceiveMessage", user, message);
     }

     //定於一個通訊管道,用來管理我們和客戶端的連線
     //1、客戶端呼叫 GetLatestCount,就像訂閱
     public async Task GetLatestCount(string random)
     {
         //2、服務端主動向客戶端傳送資料,名字千萬不能錯
         await Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData());
         
         //3、客戶端再透過 ReceiveUpdate ,來接收

     }
 }

基本的概念已經在上邊了,大家結合之前的概念,應該能看懂,看不懂也沒事兒,等看了下邊的 Vue 程式碼,就理解了。

旁白:

這裡說一下,上文中的GetLogData,這個方法,是直接把我們之前的日誌從log檔案給提取出來了,包括AOP日誌,異常日誌,Sql日誌,因為格式規則變了,如果你本地已經存在之前的錯誤日誌了,請刪了檔案重新生成,否則格式不正確會報錯。

3、配置服務 與 中介軟體

還是老規則,在netcore 中,基本只要涉及到Http請求相關的,一定要配置中介軟體,任何需要在宿主中用的服務,都需要註入:

//這個配置就太簡單了,不細說了,大家一看就知道往哪裡放
services.AddSignalR();


 app.UseMvc();

//我這個放到了 Mvc 管道下邊,註意順序
 app.UseSignalR(routes =>
 {
     //這裡要說下,為啥地址要寫 /api/xxx 
     //因為我前後端分離了,而且使用的是代理樣式,所以如果你不用/api/xxx的這個規則的話,會出現跨域問題,畢竟這個不是我的controller的路由,而且自己定義的路由
     routes.MapHub("/api/chatHub");
 });

4、跨域

這一塊大家肯定都已經配置好了,要不然不會前後端分離的,至於用前端 proxy 代理,還是後端 CORS 配置,看自己喜好吧,我更喜歡前者。

這個時候,我們的後端通道就打通了,如何驗證呢,我們在路由器直接輸入上邊的自定義路由地址即可:

 因為每次請求需要一個 ID 號的,直接訪問肯定不行,那這個 ID 我們怎麼來拿呢,彆著急,下文會說到,重點來了。

 服務端可以了,那就改配置客戶端了,其實客戶端特別簡單,就好像我們使用一個js庫外掛一樣,比如大家一定用過地圖api庫 ,直接取用js,然後new map物件即可使用了,沒錯,SignalR和它一毛一樣。

1、安裝庫依賴包

 現在 vue 也和 core 很像了,用一個東西,都需要安裝包,配置服務,再呼叫這三部曲了。

//直接在專案中執行
npm install @aspnet/signalr

我為了更好的讓大家理解這個通訊的過程,每個標題後邊,都破折號了我對這個過程的理解,大家一看就懂了。

2、新增取用——買個手機

在Admin 專案裡,我增加了一個展示日誌的頁面,大家自己看看就都懂了,然後之前是需要每次掃清的,但是這次改造成可以自推送的。

上邊也說到了,這個 SignalR 我們只需要像 map 地圖那樣,取用就行了,很簡單:

網上很坑的是,很多教程裡竟然要在 main.js 中取用,最後導致還出現了依賴 Jquery 的各種bug,大家如果無聊可以試試。

3、開始連線到中心——連上網路

 直接上程式碼:

 created: function () {
     //1、首先我們實體化一個聯結器
     this.connection = new signalR.HubConnectionBuilder()
         //然後配置通道路由
         .withUrl('/api/chatHub')
         //日誌資訊
         .configureLogging(signalR.LogLevel.Information)
         //建立
         .build();

     var thisVue = this;
     //開始連線
     thisVue.connection.start();
 },

 是不是很簡單,只需要我們在頁面初始化的時候,建立連線即可,只不過這裡有一些小問題,

 咱們的專案中,已經配置好了跨域,並且所有介面也已經成功呼叫了,但是唯獨 Hub 卻不行,情況如下:

還記得上邊咱們在 startup.cs 中配置的 hub 路由麼,如果我們不使用 /api/chatHub 會是怎麼樣呢?這裡我也簡單把錯誤的給寫出來,留作參考:

1、相對路徑,沒用代理規則:

withUrl('/axxxxx/chatHub')

是因為我們用的相對路徑,而且也沒與代理,系統會認為我們訪問的是一個頁面路由,所以 404;

2、絕對路徑,沒用代理規則

withUrl('http://localhost:8081/axxxxx/chatHub')

 

這樣就會出現代理的問題。

當然!如果你使用的是後端 CORS 機制跨域,不會這個問題的,其他的各種情況自己把握就好。

是不是我們這麼配置好了就沒事了呢,彆著急,還有要給bug,

3、伺服器Nginx代理

如果你在伺服器裡用的是 Nginx 做代理的話,可能會遇到這個問題:

大家可以看看,這個錯誤,和上邊兩個都不一樣,是已經連上了,但是去不能開啟資料 的互動 transport !神奇,簡單的看了看開源的 websockets 上提的 issues,是這麼解決的《wss: Error during WebSocket handshake: Unexpected response code: 200 》

 

好啦!這次應該沒啥問題了,繼續往下走。

4、客戶端呼叫集線器——呼叫對方

那我們現在已經連線成功了,剩下的就是呼叫集線器了,也就是上邊我們定義的 Hub 通道,用來接收日誌:

mounted() {

    thisVue.connection.on('ReceiveUpdate', function (update) {
        console.info('update success!')
        thisVue.tableData = update;
        window.clearInterval(this.t)
    })

},

5、從集線器呼叫客戶端方法——接收回應

 上邊我們是從客戶端去訂閱了一個 通道 連線,也就是說,我需要這個約定,那約定成功後,就需要接收來自伺服器的通訊傳回結果了,

thisVue.connection.on('ReceiveUpdate', function (update) {
    console.info('update success!')
    thisVue.tableData = update;//將傳回的資料,賦值給當前頁面data
})

這個時候我們掃清頁面,已經能看到訊息了,然後我們在看看介面請求:

是不是很熟悉!沒錯,這個就是我們上邊說到的那個 ID ,不記得的往上看,這個是自動生成的,而且不隨著訊息推送而變化,只有每次請求重新連線的時候,才會變化。

好啦,這樣我們就成功了在頁面上展示出了我們的資料,BUT!別慌,好像還沒有完成,因為我們現在僅僅是展示了出來,還沒有實現推送啊!彆著急,既然一次能顯示,那多次也能顯示。

6、每次更新日誌,推送到客戶端——實時簡訊

這個就很簡單了,我們只需要在每次日誌產生的時候,來推送出來即可,舉個全域性異常的慄子吧:

先註入我們的通道背景關係:

 private readonly IHubContext _hubContext;

 public GlobalExceptionsFilter(IHostingEnvironment env, ILoggerHelper loggerHelper,
IHubContext hubContext)
 {
     _env = env;
     _loggerHelper = loggerHelper;
     _hubContext = hubContext;
 }

然後直接使用:

//採用log4net 進行錯誤日誌記錄

_loggerHelper.Error(json.Message,

WriteLog(json.Message, context.Exception));


_hubContext.Clients.All.SendAsync(
ReceiveUpdate, LogLock.GetLogData()).Wait();

這樣,每次我們操作的時候,就會觸發生成日誌的功能,同時再觸發推送功能,就這樣,我們把訊息及時的推送了出去,達到了目的,實現了文章開頭的功能。

如果想中斷連線,只需要頁面關閉的時候,執行 connection.stop() 即可。

 在文章開頭,我說了幾個場景,其他的不好實現,先來個模擬登入吧,就是把使用者名稱密碼傳到後臺,然後後臺將結果推送回來。

具體的流程就不說了,和上邊的是一樣的,只是很簡單的一個動作,接收下資料即可,

今天很簡單的實現了兩個小功能,一個是模擬登入,一個是實時推送訊息,大家學會了麼,這裡有幾個問題,大家可以思考思考:

1、SignalR到底能在平時開發中,使用哪些地方?

2、服務中心是如何將訊息發出去的?

3、客戶端是如何來訂閱某一個通道集線器的?

4、SignalR的底層原理是什麼?

5、如何關閉連線?

NetCore https://github.com/anjoy8/Blog.Core

       Vue https://github.com/anjoy8/Blog.Admin

已同步到看一看
贊(0)

分享創造快樂