SignalR 在 React/Go 技术栈的实践

开发 前端
有个前后端分离的运维开发web平台, 后端会间隔5分钟同步一次数据,现在需要将最新一次同步的时间推送到web前端。

[[429700]]

本文转载自微信公众号「精益码农」,作者有态度的马甲 。转载本文请联系精益码农公众号。

一.背景

有个前后端分离的运维开发web平台, 后端会间隔5分钟同步一次数据,现在需要将最新一次同步的时间推送到web前端。

说到[web服务端推送],立马想到SignalR,(我头脑中一直有技术体系, 但一直没实践过。)

SignalR是微软推出的实时通信标准框架,内部封装了 websocket、服务端发送事件、长轮询, 可以算是实时通信的大杀器,传送门。

实际编码就是react写SignalR客户端,golang写SignalR服务端,盲猜有对应的轮子。

二.撸起袖子干

果然, 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接口的几个方法, 你还可以为集线器添加一些自定义方法。

  1. package services 
  2.  
  3. import ( 
  4.  "github.com/philippseith/signalr" 
  5.  log "github.com/sirupsen/logrus" 
  6.  "time" 
  7.  
  8. type AppHub struct{ 
  9.   signalr.Hub 
  10.  
  11. func (h *AppHub) OnConnected(connectionID string) { 
  12.  // fmt.Printf("%s connected\n", connectionID) 
  13.  log.Infoln(connectionID," connected\n" ) 
  14.  
  15. func (h *AppHub) OnDisconnected(connectionID string) { 
  16.  log.Infoln(connectionID," disconnected\n"
  17.  
  18. // 客户端调用的函数, 本例不用 
  19. func (h *AppHub) Send(message string) { 
  20.  h.Clients().All().Send("receive"time.Now().Format("2006/01/02 15:04:05") ) 

(3) 初始化集线器, 并在特定地址监听signalr请求。

这个库将signalr监听服务抽象为独立的hubServer

  1. shub := services.AppHub{} 
  2.  
  3. sHubSrv,err:= signalr.NewServer(context.TODO(), 
  4.   signalr.UseHub(&shub), // 这是单例hub 
  5.   signalr.KeepAliveInterval(2*time.Second), 
  6.   signalr.Logger(kitlog.NewLogfmtLogger(os.Stderr), true)) 
  7.  sHubSrv.MapHTTP(mux, "/realtime"

(4) 利用sHubServer在合适业务代码位置向web客户端推送数据。

  1. if clis:= s.sHubServer.HubClients(); clis!= nil { 
  2.     c:= clis.All() 
  3.     if  c!= nil { 
  4.      c.Send("receive",ts.Format("2006/01/02 15:04:05")) 
  5.     } 
  6.    } 

注意:上面的receive方法是后面react客户端需要监听的JavaScript事件名。

react客户端

前端菜鸡,跟着官方示例琢磨了好几天。

(1) 添加@microsoft/signalr 包

(2) 在组件挂载事件componentDidMount初始化signalr连接

实际也就是向服务端baseUrl建立HubConnection,注册receive事件,等待服务端推送。

  1. import React from 'react'
  2. import { 
  3.   JsonHubProtocol, 
  4.   HubConnectionState, 
  5.   HubConnectionBuilder, 
  6.   HttpTransportType, 
  7.   LogLevel, 
  8. from '@microsoft/signalr'
  9.  
  10. class Clock extends React.Component { 
  11.     constructor(props) { 
  12.       super(props); 
  13.       this.state = { 
  14.         message:''
  15.         hubConnection: null
  16.       }; 
  17.     } 
  18.    
  19.     componentDidMount() { 
  20.       const connection = new HubConnectionBuilder() 
  21.         .withUrl(process.env.REACT_APP_APIBASEURL+"realtime", { 
  22.         }) 
  23.         .withAutomaticReconnect() 
  24.         .withHubProtocol(new JsonHubProtocol()) 
  25.         .configureLogging(LogLevel.Information) 
  26.         .build(); 
  27.   
  28.     // Note: to keep the connection open the serverTimeout should be 
  29.     // larger than the KeepAlive value that is set on the server 
  30.     // keepAliveIntervalInMilliseconds default is 15000 and we are using default 
  31.     // serverTimeoutInMilliseconds default is 30000 and we are using 60000 set below 
  32.         connection.serverTimeoutInMilliseconds = 60000; 
  33.   
  34.     // re-establish the connection if connection dropped 
  35.         connection.onclose(error => { 
  36.             console.assert(connection.state === HubConnectionState.Disconnected); 
  37.             console.log('Connection closed due to error. Try refreshing this page to restart the connection', error); 
  38.         }); 
  39.      
  40.         connection.onreconnecting(error => { 
  41.             console.assert(connection.state === HubConnectionState.Reconnecting); 
  42.             console.log('Connection lost due to error. Reconnecting.', error); 
  43.         }); 
  44.      
  45.         connection.onreconnected(connectionId => { 
  46.             console.assert(connection.state === HubConnectionState.Connected); 
  47.             console.log('Connection reestablished. Connected with connectionId', connectionId); 
  48.         }); 
  49.          
  50.         this.setState({ hubConnection: connection}) 
  51.  
  52.         this.startSignalRConnection(connection).then(()=> { 
  53.               if(connection.state === HubConnectionState.Connected) { 
  54.                 connection.invoke('RequestSyncTime').then(val => { 
  55.                   console.log("Signalr get data first time:",val); 
  56.                   this.setState({ message:val }) 
  57.                 }) 
  58.               } 
  59.         }) ; 
  60.  
  61.         connection.on('receive', res => { 
  62.           console.log("SignalR get hot res:", res) 
  63.             this.setState({ 
  64.               message:res 
  65.             }); 
  66.         }); 
  67.     } 
  68.    
  69.     startSignalRConnection = async connection => { 
  70.       try { 
  71.           await connection.start(); 
  72.           console.assert(connection.state === HubConnectionState.Connected); 
  73.           console.log('SignalR connection established'); 
  74.       } catch (err) { 
  75.           console.assert(connection.state === HubConnectionState.Disconnected); 
  76.           console.error('SignalR Connection Error: ', err); 
  77.           setTimeout(() => this.startSignalRConnection(connection), 5000); 
  78.       } 
  79.     }; 
  80.    
  81.     render() { 
  82.       return ( 
  83.         <div style={{width: '300px',float:'left',marginLeft:'10px'}} > 
  84.           <h4>最新同步完成时间: {this.state.message}  </h4> 
  85.         </div> 
  86.       ); 
  87.     } 
  88.   } 
  89.  
  90. export  default  Clock; 

(3) 将该react组件插入到web前端页面

三.效果分析

最后的效果如图:

效果分析:

(1) web客户端与服务器协商 传输方式http://localhost:9598/realtime/negotiate?negotiateVersion=1,

返回可用的传输方式和连接标识ConnectionId。

  1.     "connectionId""hkSNQT-pGpZ9E6tuMY9rRw=="
  2.     "availableTransports": [{ 
  3.         "transport""WebSockets"
  4.         "transferFormats": ["Text""Binary"
  5.     }, { 
  6.         "transport""ServerSentEvents"
  7.         "transferFormats": ["Text"
  8.     }] 

(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

 

责任编辑:武晓燕 来源: 精益码农
相关推荐

2017-11-22 13:01:03

Go技术栈构建

2019-09-23 19:30:27

reduxreact.js前端

2023-10-26 07:37:18

ReactVue项目

2020-09-17 06:42:31

ReactStoreon前端

2021-10-18 09:08:27

Go分段栈连续栈

2023-07-21 01:12:30

Reactfalse​变量

2023-06-07 08:32:32

引擎技术while

2019-07-20 23:30:48

开发技能代码

2017-02-28 21:57:05

React组件

2023-02-28 12:12:21

语音识别技术解码器

2019-07-22 10:42:11

React组件前端

2017-09-07 15:53:51

Go支付Java

2023-12-05 15:58:06

React开发

2020-09-28 10:05:57

数据工具技术

2018-09-17 08:31:08

容器Docker雪球

2020-01-07 15:40:43

React前端技术准则

2018-06-06 10:50:18

容器开源

2022-04-06 15:58:25

火山引擎差分隐私LDPDC

2020-06-03 16:50:24

TypeScriptReact前端

2022-08-19 09:01:59

ReactTS类型
点赞
收藏

51CTO技术栈公众号