SignalR 在React/GO技术栈的生产应用
哼哧哼哧半年,优化改进了一个运维开发web平台。
本文记录SignalR在react/golang 技术栈的生产小实践。
一. 背景
有个前后端分离的运维开发web平台, 后端会间隔1分钟同步一次数据,现在需要将最新一次同步的时间推送到web前端。 说到[web服务端推送],立马想到SignalR。
- signalr是微软推出的实时通信标准框架,内部封装了 websocket、服务端发送事件、长轮询,一开始双方发起协商, 确定即将要用的实时传输方式(优先websocket)。
- signalr 有两种通信模型:大部分应用都使用高级的hub模型。
- 持久连接API : 提供低级的连接ID,这里的连接表示用于发送单个接收者、分组或广播消息的简单端点。
- hubs: 远程过程调用, 双端调用,就像函数就在本地。
- signalR提供了管理实例、连接、失连, 分组管控的API。
本例就是react写signalr客户端,golang写signalr服务端,盲猜有对应的轮子。
二. 撸起袖子干
果然, signalr的作者David Fowler实现了node、go版本, 这位老哥是.NET技术栈如雷贯耳的大牛。
但是他的仓库很久不更了,某德国大佬在此基础上fork新的github仓库。
本例主要你演示 服务端向客户端推送,最关键的一个概念是集线器Hub,其实也就是RPC领域常说的客户端代理。
服务端在baseUrl
上建立signalr的监听地址; 客户端连接并注册receive
事件;
服务端在适当时候通过hubServer
向HubClients发送数据。
三. go服务端
-
添加golang pgk:
go get github.com/philippseith/signalr -
定义客户端集线器hub,这里要实现
HubInterface
接口的几个方法, 你还可以为集线器添加一些自定义方法。
package services import ( "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") } // 客户端能调用的函数, 客户端会是这样: connection.invoke('RequestSyncTime',"msg"); func (h *AppHub) RequestSyncTime(message string) { h.Clients().All().Send("receive", time.Now().Format("2006/01/02 15:04:05") ) }
上面定义了RequestSyncTime 方法,可以由客户端rpc; 同时向客户端receive方法推送了数据。
- 初始化客户端集线器, 并在特定地址监听signalr请求。
这个库将signalr监听服务抽象为独立的hubServer
shub := services.AppHub{} sHubSrv,err:= signalr.NewServer(context.TODO(), signalr.UseHub(&shub), // 这是单例hub signalr.KeepAliveInterval(2*time.Second), signalr.Logger(kitlog.NewLogfmtLogger(os.Stderr), true)) sHubSrv.MapHTTP(mux, "/realtime")
- 利用
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 below connection.serverTimeoutInMilliseconds = 60000; // re-establish the connection if connection dropped connection.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 => { // RequestSyncTime 是服务端定义的函数,客户端远程过程调用 console.log("Signalr get data first time:",val); this.setState({ message:val }) }) } }) ; connection.on('receive', res => { // 客户端注册的receive 函数 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前端页面
五. 效果分析
最后的效果如图:
效果分析:
(1) 客户端与服务器发起post协商请求
http://localhost:9598/realtime/negotiate?negotiateVersion=1
返回可用的传输方式和连接标识`ConnectionId`。 { "connectionId": "hkSNQT-pGpZ9E6tuMY9rRw==", "availableTransports": [{ "transport": "WebSockets", "transferFormats": ["Text", "Binary"] }, { "transport": "ServerSentEvents", "transferFormats": ["Text"] }] }
(2) web客户端利用上面的ConnectionId
向特定的服务器地址/realtime
连接,建立传输通道,默认优先websocket。
wss://api.rosenbridge.qa.xxxx.com/realtime?id=hkSNQT-pGpZ9E6tuMY9rRw==
服务端的h.Clients().All().Send("receive", time.Now().Format("2006/01/02 15:04:05") )
产生了如下的传输数据:
{“type”:1,“target”:“receive”, "arguments":[2021/10/18 11:13:28]}
websocket 请求是基于http Get请求握手后,要求升级协议达到的长连接,数据传递在[消息]标签页, 我们整体看到是ws:get请求。
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15423647.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-10-19 学完这篇依赖注入,与面试官扯皮就没有问题了。