一.背景
有个前后端分离的运维开发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接口的几个方法, 你还可以为集线器添加一些自定义方法。
- packageservices
- import(
- "github.com/philippseith/signalr"
- log"github.com/sirupsen/logrus"
- "time"
- )
- typeAppHubstruct{
- signalr.Hub
- }
- func(h*AppHub)OnConnected(connectionIDstring){
- //fmt.Printf("%sconnected\\n",connectionID)
- log.Infoln(connectionID,"connected\\n")
- }
- func(h*AppHub)OnDisconnected(connectionIDstring){
- log.Infoln(connectionID,"disconnected\\n")
- }
- //客户端调用的函数,本例不用
- func(h*AppHub)Send(messagestring){
- h.Clients().All().Send("receive",time.Now().Format("2006/01/0215:04:05"))
- }
(3) 初始化集线器, 并在特定地址监听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")
(4) 利用sHubServer在合适业务代码位置向web客户端推送数据。
- ifclis:=s.sHubServer.HubClients();clis!=nil{
- c:=clis.All()
- ifc!=nil{
- c.Send("receive",ts.Format("2006/01/0215:04:05"))
- }
- }
注意:上面的receive方法是后面react客户端需要监听的JavaScript事件名。
react客户端
前端菜鸡,跟着官方示例琢磨了好几天。
(1) 添加@microsoft/signalr 包
(2) 在组件挂载事件componentDidMount初始化signalr连接
实际也就是向服务端baseUrl建立HubConnection,注册receive事件,等待服务端推送。
- importReactfrom'react';
- import{
- JsonHubProtocol,
- HubConnectionState,
- HubConnectionBuilder,
- HttpTransportType,
- LogLevel,
- }from'@microsoft/signalr';
- classClockextendsReact.Component{
- constructor(props){
- super(props);
- this.state={
- message:'',
- hubConnection:null,
- };
- }
- componentDidMount(){
- constconnection=newHubConnectionBuilder()
- .withUrl(process.env.REACT_APP_APIBASEURL+"realtime",{
- })
- .withAutomaticReconnect()
- .withHubProtocol(newJsonHubProtocol())
- .configureLogging(LogLevel.Information)
- .build();
- //Note:tokeeptheconnectionopentheserverTimeoutshouldbe
- //largerthantheKeepAlivevaluethatissetontheserver
- //keepAliveIntervalInMillisecondsdefaultis15000andweareusingdefault
- //serverTimeoutInMillisecondsdefaultis30000andweareusing60000setbelow
- connection.serverTimeoutInMilliseconds=60000;
- //re-establishtheconnectionifconnectiondropped
- connection.onclose(error=>{
- console.assert(connection.state===HubConnectionState.Disconnected);
- console.log('Connectionclosedduetoerror.Tryrefreshingthispagetorestarttheconnection',error);
- });
- connection.onreconnecting(error=>{
- console.assert(connection.state===HubConnectionState.Reconnecting);
- console.log('Connectionlostduetoerror.Reconnecting.',error);
- });
- connection.onreconnected(connectionId=>{
- console.assert(connection.state===HubConnectionState.Connected);
- console.log('Connectionreestablished.ConnectedwithconnectionId',connectionId);
- });
- this.setState({hubConnection:connection})
- this.startSignalRConnection(connection).then(()=>{
- if(connection.state===HubConnectionState.Connected){
- connection.invoke('RequestSyncTime').then(val=>{
- console.log("Signalrgetdatafirsttime:",val);
- this.setState({message:val})
- })
- }
- });
- connection.on('receive',res=>{
- console.log("SignalRgethotres:",res)
- this.setState({
- message:res
- });
- });
- }
- startSignalRConnection=asyncconnection=>{
- try{
- awaitconnection.start();
- console.assert(connection.state===HubConnectionState.Connected);
- console.log('SignalRconnectionestablished');
- }catch(err){
- console.assert(connection.state===HubConnectionState.Disconnected);
- console.error('SignalRConnectionError:',err);
- setTimeout(()=>this.startSignalRConnection(connection),5000);
- }
- };
- render(){
- return(
- <divstyle={{width:'300px',float:'left',marginLeft:'10px'}}>
- <h4>最新同步完成时间:{this.state.message}</h4>
- </div>
- );
- }
- }
- exportdefaultClock;
(3) 将该react组件插入到web前端页面
三.效果分析
最后的效果如图:
效果分析:
(1) web客户端与服务器协商 传输方式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。
以上网络交互,大部分会通过SignalR框架自动完成。
源码:Github Demo[2]
引用链接
[1] Github仓库: https://github.com/philippseith/signalr
[2] Github Demo: https://github.com/zaozaoniao/SignalR-apply-to-react-and-golang
原文链接:https://mp.weixin.qq.com/s/O8ysCDsIG8mjarv_A31BIw






