NetCore + SignalR 实现日志消息推送
哈嘍大家周一好呀,感覺(jué)好久沒(méi)有寫(xiě)文章了,上周出差了一次,感覺(jué)還是比坐辦公室好的多,平時(shí)在讀一本書(shū)《時(shí)生》,感興趣的可以看看?......
這幾天翻看 NetCore 相關(guān)知識(shí)擴(kuò)展的時(shí)候,發(fā)現(xiàn)了久違的一個(gè)知識(shí)點(diǎn) —— SignalR ,為啥說(shuō)久違呢,因?yàn)槿ツ甑臅r(shí)候,我在公司的項(xiàng)目里就想用了,后來(lái)組員說(shuō)他學(xué)學(xué)看,也沒(méi)有了下文,我也就耽擱了,昨天突然看到這個(gè)了,想著正好看看吧,盡量落地到 NetCore 項(xiàng)目上,當(dāng)時(shí)我很自信的以為這個(gè)技術(shù)很老了,應(yīng)該用的人很多,可是天不遂人愿,在.Net MVC中使用的很多有六成,在NetCore 的小demo有兩成,NetCore + Vue 一起使用的就是寥寥無(wú)幾了,而且更多的是仿照官網(wǎng)的,好吧,我就簡(jiǎn)單寫(xiě)一個(gè)吧,希望對(duì)大家有所幫助,盡量將這個(gè)技術(shù)落地。
你一定很好奇,為啥要學(xué) SignalR ,或者說(shuō)它有啥作用,那我先說(shuō)幾個(gè)場(chǎng)景,你就知道了:
1、用戶登錄處理相關(guān)場(chǎng)景;?//平時(shí)我們是ajax請(qǐng)求,等待后端處理,然后返回 true ,根據(jù)返回結(jié)果相應(yīng)處理;
2、后端(c#)強(qiáng)制用戶退出登錄(js);?//這個(gè)后端還真沒(méi)有做過(guò),沒(méi)啥思路
3、用戶支付訂單,等待成功后跳轉(zhuǎn);?//我以前用的是 ajax 輪詢,額。。。
4、給用戶發(fā)消息,或網(wǎng)頁(yè)內(nèi)簡(jiǎn)單的聊天;
5、秒殺用戶名單,在頁(yè)面實(shí)時(shí)進(jìn)行滑動(dòng)展示;
大家從這幾個(gè)栗子中,可以看到一個(gè)共性:
就是想用后端來(lái)操作前端,也就是說(shuō)可以通過(guò) 服務(wù)端代碼,來(lái)控制前端 js 事件,來(lái)實(shí)現(xiàn)響應(yīng)式的實(shí)時(shí)場(chǎng)景過(guò)程,用戶完全不用做任何操作,或者做少量的操作就能實(shí)現(xiàn)更多的效果。
大家可以先自己用自己平時(shí)的想法和經(jīng)驗(yàn)來(lái)實(shí)現(xiàn)上邊的場(chǎng)景,當(dāng)然并不是一定使用 SignalR ,Scoket 現(xiàn)在也是很火,本文就是簡(jiǎn)單說(shuō)說(shuō) SignalR 的基礎(chǔ)用法,想了想,怎么才能讓這個(gè)技術(shù)落地,最后決定將全部的操作日志通過(guò) SignalR 的形式在Admin后臺(tái)展示出來(lái)吧,以后也試試強(qiáng)制登錄的功能
1、基本概念
本文重點(diǎn)說(shuō)如何使用,但是為了文章的完整性,還是粘貼了一些概念講解,參考《ASP.NET Core SignalR 簡(jiǎn)介》,更多的知識(shí)點(diǎn)請(qǐng)自行研究吧,網(wǎng)上這種概念類文章很多:
ASP.NET Core SignalR 是一個(gè)開(kāi)源代碼庫(kù),它簡(jiǎn)化了向應(yīng)用添加實(shí)時(shí) Web 功能的過(guò)程。?實(shí)時(shí) Web 功能使服務(wù)器端代碼能夠即時(shí)將內(nèi)容推送到客戶端。
SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作為底層傳輸方式。
SignalR 的適用對(duì)象:
-
需要來(lái)自服務(wù)器的高頻率更新的應(yīng)用。?例如:游戲、社交網(wǎng)絡(luò)、投票、拍賣、地圖和 GPS 應(yīng)用。
-
儀表板和監(jiān)視應(yīng)用。?示例包括公司儀表板、銷售狀態(tài)即時(shí)更新或行程警示。
-
協(xié)作應(yīng)用。?協(xié)作應(yīng)用的示例包括白板應(yīng)用和團(tuán)隊(duì)會(huì)議軟件。
-
需要通知的應(yīng)用。?社交網(wǎng)絡(luò)、電子郵件、聊天、游戲、行程警示以及許多其他應(yīng)用都使用通知。
SignalR 提供了一個(gè)用于創(chuàng)建服務(wù)器到客戶端 《遠(yuǎn)程過(guò)程調(diào)用(RPC》的 API。?RPC 通過(guò)服務(wù)器端 .NET Core 代碼調(diào)用客戶端上的 JavaScript 函數(shù)。
以下是 ASP.NET Core SignalR 的一些功能:
-
自動(dòng)管理連接。
-
同時(shí)向所有連接的客戶端發(fā)送消息。?例如,聊天室。
-
將消息發(fā)送到特定的客戶端或客戶端組。
-
擴(kuò)展以處理增加的流量。
為啥要使用它,因?yàn)樗俏④浀摹?/strong>?
2、支持平臺(tái)
服務(wù)端:ASP.NET Core SignalR 適用于 ASP.NET Core 支持的任何服務(wù)器平臺(tái)。
JS 客戶端:需要支持NodeJS 8+、或者常見(jiàn)主流瀏覽器都支持。
Java 客戶端:支持Java 8或更高版本。
Net 客戶端:可以在 ASP.NET Core 支持的任何平臺(tái)上運(yùn)行。?例如,?Xamarin 開(kāi)發(fā)人員可以使用 SignalR用于構(gòu)建 Android 應(yīng)用程序使用 Xamarin.Android 8.4.0.1 或更高版本和 iOS 應(yīng)用程序使用 Xamarin.iOS 11.14.0.4 或更高版本。如果服務(wù)器運(yùn)行 IIS,Websocket 傳輸要求安裝 IIS 8.0 或更高版本在 Windows Server 2012 或更高版本。?其他傳輸在所有平臺(tái)上都受支持。
3、回落機(jī)制
參考文章《SignalR簡(jiǎn)介及使用》
SignalR使用的三種底層傳輸技術(shù)分別是Web Socket, Server Sent Events 和 Long Polling;
其中Web Socket僅支持比較現(xiàn)代的瀏覽器,Web服務(wù)器也不能太老;
而Server Sent Events 情況可能好一點(diǎn), 但是也存在同樣的問(wèn)題。
Web Socket是最好的最有效的傳輸方式, 如果瀏覽器或Web服務(wù)器不支持它的話, 就會(huì)降級(jí)使用SSE, 實(shí)在不行就用Long Polling;
一旦建立連接, SignalR就會(huì)開(kāi)始發(fā)送keep alive消息, 來(lái)檢查連接是否還正常, 如果有問(wèn)題, 就會(huì)拋出異常;
因?yàn)镾ignalR是抽象于三種傳輸方式的上層, 所以無(wú)論底層采用的哪種方式, SignalR的用法都是一樣的;
SignalR默認(rèn)采用這種回落機(jī)制來(lái)進(jìn)行傳輸和連接。但是也可以禁用回落機(jī)制, 只采用其中一種傳輸方式。
4、Hub組件
Hub是SignalR的一個(gè)組件, 它運(yùn)行在ASP.NET Core應(yīng)用里. 所以它是服務(wù)器端的一個(gè)類;
Hub使用RPC接受從客戶端發(fā)來(lái)的消息, 也能把消息發(fā)送給客戶端, 所以它就是一個(gè)通信用的Hub;
在ASP.NET Core里, 自己創(chuàng)建的Hub類需要繼承于基類Hub;
在Hub類里面, 我們就可以調(diào)用所有客戶端上的方法了, 同樣客戶端也可以調(diào)用Hub類里的方法;
之前說(shuō)過(guò)方法調(diào)用的時(shí)候可以傳遞復(fù)雜參數(shù), SignalR可以將參數(shù)序列化和反序列化, 這些參數(shù)被序列化的格式叫做Hub 協(xié)議,所以Hub協(xié)議就是一種用來(lái)序列化和反序列化的格式;
Hub協(xié)議的默認(rèn)協(xié)議是JSON, 還支持另外一個(gè)協(xié)議是MessagePack, MessagePack是二進(jìn)制格式的, 它比JSON更緊湊, 而且處理起來(lái)更簡(jiǎn)單快速, 因?yàn)樗嵌M(jìn)制的;
此外, SignalR也可以擴(kuò)展使用其它協(xié)議。
好啦,復(fù)雜而又枯燥的概念說(shuō)完了,接下來(lái)咱們開(kāi)始動(dòng)手寫(xiě)代碼了!(額概念還是要看的?)
既然要實(shí)現(xiàn)實(shí)時(shí)交互,肯定得有服務(wù)端,那我們就直接在 Blog.Core 項(xiàng)目上,進(jìn)行處理吧
1、引用 SignalR 包
為了以后好拓展,我就把 SignalR 中心,也可以是通訊管道定義到了 Common 層,當(dāng)然可以自定義任意層。
//請(qǐng)看清,還有一個(gè) Net 版本的,但是也能用,還是用 core 版本的吧
Install-Package Microsoft.AspNetCore.SignalR
2、聲明 Hub 管道——集線器
在 Blog.Core.Common 層,新建一個(gè) Hubs 文件夾,然后添加一個(gè) ChatHub 類:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
//定于一個(gè)通訊管道,用來(lái)管理我們和客戶端的連接
//1、客戶端調(diào)用 GetLatestCount,就像訂閱
public async Task GetLatestCount(string random)
{
//2、服務(wù)端主動(dòng)向客戶端發(fā)送數(shù)據(jù),名字千萬(wàn)不能錯(cuò)
await Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData());
//3、客戶端再通過(guò) ReceiveUpdate ,來(lái)接收
}
}
基本的概念已經(jīng)在上邊了,大家結(jié)合之前的概念,應(yīng)該能看懂,看不懂也沒(méi)事兒,等看了下邊的 Vue 代碼,就理解了。
旁白:
這里說(shuō)一下,上文中的GetLogData,這個(gè)方法,是直接把我們之前的日志從log文件給提取出來(lái)了,包括AOP日志,異常日志,Sql日志,因?yàn)?strong>格式規(guī)則變了,如果你本地已經(jīng)存在之前的錯(cuò)誤日志了,請(qǐng)刪了文件重新生成,否則格式不正確會(huì)報(bào)錯(cuò)。
3、配置服務(wù) 與 中間件
還是老規(guī)則,在netcore 中,基本只要涉及到Http請(qǐng)求相關(guān)的,一定要配置中間件,任何需要在宿主中用的服務(wù),都需要注入:
//這個(gè)配置就太簡(jiǎn)單了,不細(xì)說(shuō)了,大家一看就知道往哪里放
services.AddSignalR();
app.UseMvc();
//我這個(gè)放到了 Mvc 管道下邊,注意順序
app.UseSignalR(routes =>
{
//這里要說(shuō)下,為啥地址要寫(xiě) /api/xxx
//因?yàn)槲仪昂蠖朔蛛x了,而且使用的是代理模式,所以如果你不用/api/xxx的這個(gè)規(guī)則的話,會(huì)出現(xiàn)跨域問(wèn)題,畢竟這個(gè)不是我的controller的路由,而且自己定義的路由
routes.MapHub<ChatHub>("/api/chatHub");
});
4、跨域
這一塊大家肯定都已經(jīng)配置好了,要不然不會(huì)前后端分離的,至于用前端 proxy 代理,還是后端 CORS 配置,看自己喜好吧,我更喜歡前者。
這個(gè)時(shí)候,我們的后端通道就打通了,如何驗(yàn)證呢,我們?cè)诼酚善髦苯虞斎肷线叺淖远x路由地址即可:
?因?yàn)槊看握?qǐng)求需要一個(gè) ID 號(hào)的,直接訪問(wèn)肯定不行,那這個(gè) ID 我們?cè)趺磥?lái)拿呢,別著急,下文會(huì)說(shuō)到,重點(diǎn)來(lái)了。
?服務(wù)端可以了,那就改配置客戶端了,其實(shí)客戶端特別簡(jiǎn)單,就好像我們使用一個(gè)js庫(kù)插件一樣,比如大家一定用過(guò)地圖api庫(kù) ,直接引用js,然后new map對(duì)象即可使用了,沒(méi)錯(cuò),SignalR和它一毛一樣。
1、安裝庫(kù)依賴包
?現(xiàn)在 vue 也和 core 很像了,用一個(gè)東西,都需要安裝包,配置服務(wù),再調(diào)用這三部曲了。
//直接在項(xiàng)目中執(zhí)行
npm install @aspnet/signalr
我為了更好的讓大家理解這個(gè)通訊的過(guò)程,每個(gè)標(biāo)題后邊,都破折號(hào)了我對(duì)這個(gè)過(guò)程的理解,大家一看就懂了。
2、添加引用——買個(gè)手機(jī)
在Admin 項(xiàng)目里,我增加了一個(gè)展示日志的頁(yè)面,大家自己看看就都懂了,然后之前是需要每次刷新的,但是這次改造成可以自推送的。
上邊也說(shuō)到了,這個(gè) SignalR 我們只需要像 map 地圖那樣,引用就行了,很簡(jiǎn)單:
網(wǎng)上很坑的是,很多教程里竟然要在 main.js 中引用,最后導(dǎo)致還出現(xiàn)了依賴 Jquery 的各種bug,大家如果無(wú)聊可以試試。
3、開(kāi)始連接到中心——連上網(wǎng)絡(luò)
?直接上代碼:
created: function () {
//1、首先我們實(shí)例化一個(gè)連接器
this.connection = new signalR.HubConnectionBuilder()
//然后配置通道路由
.withUrl('/api/chatHub')
//日志信息
.configureLogging(signalR.LogLevel.Information)
//創(chuàng)建
.build();
var thisVue = this;
//開(kāi)始連接
thisVue.connection.start();
},
?是不是很簡(jiǎn)單,只需要我們?cè)陧?yè)面初始化的時(shí)候,創(chuàng)建連接即可,只不過(guò)這里有一些小問(wèn)題,
?咱們的項(xiàng)目中,已經(jīng)配置好了跨域,并且所有接口也已經(jīng)成功調(diào)用了,但是唯獨(dú) Hub 卻不行,情況如下:
還記得上邊咱們?cè)?startup.cs 中配置的 hub 路由么,如果我們不使用 /api/chatHub 會(huì)是怎么樣呢?這里我也簡(jiǎn)單把錯(cuò)誤的給寫(xiě)出來(lái),留作參考:
1、相對(duì)路徑,沒(méi)用代理規(guī)則:
withUrl('/axxxxx/chatHub')是因?yàn)槲覀冇玫南鄬?duì)路徑,而且也沒(méi)與代理,系統(tǒng)會(huì)認(rèn)為我們?cè)L問(wèn)的是一個(gè)頁(yè)面路由,所以 404;
2、絕對(duì)路徑,沒(méi)用代理規(guī)則
withUrl('http://localhost:8081/axxxxx/chatHub')?
這樣就會(huì)出現(xiàn)代理的問(wèn)題。
當(dāng)然!如果你使用的是后端 CORS 機(jī)制跨域,不會(huì)這個(gè)問(wèn)題的,其他的各種情況自己把握就好。
是不是我們這么配置好了就沒(méi)事了呢,別著急,還有要給bug,
3、服務(wù)器Nginx代理
如果你在服務(wù)器里用的是 Nginx 做代理的話,可能會(huì)遇到這個(gè)問(wèn)題:
大家可以看看,這個(gè)錯(cuò)誤,和上邊兩個(gè)都不一樣,是已經(jīng)連上了,但是去不能開(kāi)啟數(shù)據(jù) 的交互 transport !神奇,簡(jiǎn)單的看了看開(kāi)源的 websockets 上提的 issues,是這么解決的《wss: Error during WebSocket handshake: Unexpected response code: 200?》
?
好啦!這次應(yīng)該沒(méi)啥問(wèn)題了,繼續(xù)往下走。
4、客戶端調(diào)用集線器——呼叫對(duì)方
那我們現(xiàn)在已經(jīng)連接成功了,剩下的就是調(diào)用集線器了,也就是上邊我們定義的 Hub 通道,用來(lái)接收日志:
mounted() {
thisVue.connection.on('ReceiveUpdate', function (update) {
console.info('update success!')
thisVue.tableData = update;
window.clearInterval(this.t)
})
},
5、從集線器調(diào)用客戶端方法——接收回應(yīng)
?上邊我們是從客戶端去訂閱了一個(gè) 通道 連接,也就是說(shuō),我需要這個(gè)約定,那約定成功后,就需要接收來(lái)自服務(wù)器的通訊返回結(jié)果了,
thisVue.connection.on('ReceiveUpdate', function (update) {
console.info('update success!')
thisVue.tableData = update;//將返回的數(shù)據(jù),賦值給當(dāng)前頁(yè)面data
})
這個(gè)時(shí)候我們刷新頁(yè)面,已經(jīng)能看到消息了,然后我們?cè)诳纯唇涌谡?qǐng)求:
是不是很熟悉!沒(méi)錯(cuò),這個(gè)就是我們上邊說(shuō)到的那個(gè) ID ,不記得的往上看,這個(gè)是自動(dòng)生成的,而且不隨著消息推送而變化,只有每次請(qǐng)求重新連接的時(shí)候,才會(huì)變化。?
好啦,這樣我們就成功了在頁(yè)面上展示出了我們的數(shù)據(jù),BUT!別慌,好像還沒(méi)有完成,因?yàn)槲覀儸F(xiàn)在僅僅是展示了出來(lái),還沒(méi)有實(shí)現(xiàn)推送啊!別著急,既然一次能顯示,那多次也能顯示。
6、每次更新日志,推送到客戶端——實(shí)時(shí)短信
這個(gè)就很簡(jiǎn)單了,我們只需要在每次日志產(chǎn)生的時(shí)候,來(lái)推送出來(lái)即可,舉個(gè)全局異常的栗子吧:
先注入我們的通道上下文:
private readonly IHubContext<ChatHub> _hubContext;
public GlobalExceptionsFilter(IHostingEnvironment env, ILoggerHelper loggerHelper,
IHubContext<ChatHub> hubContext)
{
_env = env;
_loggerHelper = loggerHelper;
_hubContext = hubContext;
}
然后直接使用:
//采用log4net 進(jìn)行錯(cuò)誤日志記錄
_loggerHelper.Error(json.Message,
WriteLog(json.Message, context.Exception));
_hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait();
這樣,每次我們操作的時(shí)候,就會(huì)觸發(fā)生成日志的功能,同時(shí)再觸發(fā)推送功能,就這樣,我們把消息及時(shí)的推送了出去,達(dá)到了目的,實(shí)現(xiàn)了文章開(kāi)頭的功能。
如果想中斷連接,只需要頁(yè)面關(guān)閉的時(shí)候,執(zhí)行 connection.stop() 即可。
?在文章開(kāi)頭,我說(shuō)了幾個(gè)場(chǎng)景,其他的不好實(shí)現(xiàn),先來(lái)個(gè)模擬登錄吧,就是把用戶名密碼傳到后臺(tái),然后后臺(tái)將結(jié)果推送回來(lái)。
具體的流程就不說(shuō)了,和上邊的是一樣的,只是很簡(jiǎn)單的一個(gè)動(dòng)作,接收下數(shù)據(jù)即可,
今天很簡(jiǎn)單的實(shí)現(xiàn)了兩個(gè)小功能,一個(gè)是模擬登錄,一個(gè)是實(shí)時(shí)推送消息,大家學(xué)會(huì)了么,這里有幾個(gè)問(wèn)題,大家可以思考思考:
1、SignalR到底能在平時(shí)開(kāi)發(fā)中,使用哪些地方?
2、服務(wù)中心是如何將消息發(fā)出去的?
3、客戶端是如何來(lái)訂閱某一個(gè)通道集線器的?
4、SignalR的底層原理是什么?
5、如何關(guān)閉連接?
NetCore?https://github.com/anjoy8/Blog.Core
? ? ? ?Vue?https://github.com/anjoy8/Blog.Admin
原文地址:https://www.cnblogs.com/laozhang-is-phi/p/netcore-vue-signalr.html
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的NetCore + SignalR 实现日志消息推送的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。