SignalR在React/Go技术栈的实践
哼哧哼哧半年,優化改進了一個運維開發web平臺。
本文記錄SignalR在react/golang 技術棧的生產小實踐。
01
背景
有個前后端分離的運維開發web平臺, 后端會間隔5分鐘同步一次數據,現在需要將最新一次同步的時間推送到web前端。
說到[web服務端推送],立馬想到SignalR,(我頭腦中一直有技術體系, 但一直沒實踐過。)
SignalR是微軟推出的實時通信標準框架,內部封裝了 websocket、服務端發送事件、長輪詢, 可以算是實時通信的大殺器,傳送門。
實際編碼就是react寫SignalR客戶端,golang寫SignalR服務端,盲猜有對應的輪子。
02
擼起袖子干
果然, signalr的作者David Fowler實現了node、go版本, 這位老哥是.NET技術棧如雷貫耳的大牛:
但是他的倉庫很久不更了,某德國大佬在此基礎上開了新github倉庫[1]繼續支持。
SignalR的基本交互原理:
(1) signalR提供了一組API, 用于創建從服務端到客戶端的遠程過程調用(RPC),這個調用的具體體現是 :從服務端.NET 代碼調用位于客戶端的javascript 代碼。
(2) signalr提供了管理實例、連接、失連, 分組管控的API。
這里面最關鍵的一個概念是集線器Hub,其實也就是RPC領域常說的客戶端代理。
服務端在baseUrl上建立signalr的監聽地址;
客戶端連接并注冊receive事件;
服務端在適當時候通過hubServer向HubClients發送數據。
go服務端
(1) 添加golang pgk:go get github.com/philippseith/signalr
(2) 定義客戶端集線器hub,這里要實現HubInterface接口的幾個方法, 你還可以為集線器添加一些自定義方法。
package?servicesimport?("github.com/philippseith/signalr"log?"github.com/sirupsen/logrus""time" )type?AppHub?struct{signalr.Hub }func?(h?*AppHub)?OnConnected(connectionID?string)?{//?fmt.Printf("%s?connected\n",?connectionID)log.Infoln(connectionID,"?connected\n"?) }func?(h?*AppHub)?OnDisconnected(connectionID?string)?{log.Infoln(connectionID,"?disconnected\n") }//?客戶端調用的函數,?本例不用 func?(h?*AppHub)?Send(message?string)?{h.Clients().All().Send("receive",?time.Now().Format("2006/01/02?15:04:05")?) }(3) 初始化集線器, 并在特定地址監聽signalr請求。
這個庫將signalr監聽服務抽象為獨立的hubServer
shub?:=?services.AppHub{}sHubSrv,err:=?signalr.NewServer(context.TODO(),signalr.UseHub(&shub),?//?這是單例hubsignalr.KeepAliveInterval(2*time.Second),signalr.Logger(kitlog.NewLogfmtLogger(os.Stderr),?true))sHubSrv.MapHTTP(mux,?"/realtime")(4) 利用sHubServer在合適業務代碼位置向web客戶端推送數據。
if?clis:=?s.sHubServer.HubClients();?clis!=?nil?{c:=?clis.All()if??c!=?nil?{c.Send("receive",ts.Format("2006/01/02?15:04:05"))}}注意:上面的receive方法是后面react客戶端需要監聽的JavaScript事件名。
react客戶端
前端菜雞,跟著官方示例琢磨了好幾天。
(1) 添加@microsoft/signalr 包
(2) 在組件掛載事件componentDidMount初始化signalr連接
實際也就是向服務端baseUrl建立HubConnection,注冊receive事件,等待服務端推送。
import?React?from?'react'; import?{JsonHubProtocol,HubConnectionState,HubConnectionBuilder,HttpTransportType,LogLevel, }?from?'@microsoft/signalr';class?Clock?extends?React.Component?{constructor(props)?{super(props);this.state?=?{message:'',hubConnection:?null,};}componentDidMount()?{const?connection?=?new?HubConnectionBuilder().withUrl(process.env.REACT_APP_APIBASEURL+"realtime",?{}).withAutomaticReconnect().withHubProtocol(new?JsonHubProtocol()).configureLogging(LogLevel.Information).build();//?Note:?to?keep?the?connection?open?the?serverTimeout?should?be//?larger?than?the?KeepAlive?value?that?is?set?on?the?server//?keepAliveIntervalInMilliseconds?default?is?15000?and?we?are?using?default//?serverTimeoutInMilliseconds?default?is?30000?and?we?are?using?60000?set?belowconnection.serverTimeoutInMilliseconds?=?60000;//?re-establish?the?connection?if?connection?droppedconnection.onclose(error?=>?{console.assert(connection.state?===?HubConnectionState.Disconnected);console.log('Connection?closed?due?to?error.?Try?refreshing?this?page?to?restart?the?connection',?error);});connection.onreconnecting(error?=>?{console.assert(connection.state?===?HubConnectionState.Reconnecting);console.log('Connection?lost?due?to?error.?Reconnecting.',?error);});connection.onreconnected(connectionId?=>?{console.assert(connection.state?===?HubConnectionState.Connected);console.log('Connection?reestablished.?Connected?with?connectionId',?connectionId);});this.setState({?hubConnection:?connection})this.startSignalRConnection(connection).then(()=>?{if(connection.state?===?HubConnectionState.Connected)?{connection.invoke('RequestSyncTime').then(val?=>?{console.log("Signalr?get?data?first?time:",val);this.setState({?message:val?})})}})?;connection.on('receive',?res?=>?{console.log("SignalR?get?hot?res:",?res)this.setState({message:res});});}startSignalRConnection?=?async?connection?=>?{try?{await?connection.start();console.assert(connection.state?===?HubConnectionState.Connected);console.log('SignalR?connection?established');}?catch?(err)?{console.assert(connection.state?===?HubConnectionState.Disconnected);console.error('SignalR?Connection?Error:?',?err);setTimeout(()?=>?this.startSignalRConnection(connection),?5000);}};render()?{return?(<div?style={{width:?'300px',float:'left',marginLeft:'10px'}}?><h4>最新同步完成時間:?{this.state.message}??</h4></div>);}}export??default??Clock;(3) 將該react組件插入到web前端頁面
03
效果分析
最后的效果如圖:
效果分析:
(1) web客戶端與服務器協商 傳輸方式http://localhost:9598/realtime/negotiate?negotiateVersion=1,
返回可用的傳輸方式和連接標識ConnectionId。
(2) web客戶端利用上面的ConnectionId向特定的服務器地址/realtime連接,建立傳輸通道,默認優先websocket。
以上網絡交互,大部分會通過SignalR框架自動完成。
源碼:Github Demo[2]
引用鏈接
[1]?Github倉庫:?https://github.com/philippseith/signalr
[2]?Github Demo:?https://github.com/zaozaoniao/SignalR-apply-to-react-and-golang
●實時通信技術大亂斗
●.NET WebSocket 核心原理初體驗
●.NET gRPC核心功能初體驗
●大前端快閃四:這次使用一個舒服的姿勢插入HttpClient攔截器
●大前端快閃三:多環境靈活配置react
●大前端快閃二:react開發模式 一鍵啟動多個服務
●大前端快閃:package.json文件知多少?
點“贊”戳“在看”
體現態度很有必要!
總結
以上是生活随笔為你收集整理的SignalR在React/Go技术栈的实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# WPF MVVM模式下在主窗体显示
- 下一篇: Magicodes.IE 2.5.6.2