• SignalR 在React/GO技术栈的生产应用


    哼哧哼哧半年,优化改进了一个运维开发web平台。
    本文记录SignalR在react/golang 技术栈的生产小实践。

    1. 背景

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

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

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

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

    2. 撸起袖子干

    果然, signalr的作者David Fowler实现了node、go版本, 这位老哥是.NET技术栈如雷贯耳的大牛:


    但是他的仓库很久不更了,某德国大佬在此基础上开了新github仓库继续支持。

    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 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
    ", connectionID)
    	log.Infoln(connectionID," connected
    " )
    }
    
    func (h *AppHub) OnDisconnected(connectionID string) {
    	log.Infoln(connectionID," disconnected
    ")
    }
    
    // 客户端调用的函数, 本例不用
    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), // 这是单例hub
    		signalr.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 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 => {
                      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={{ '300px',float:'left',marginLeft:'10px'}} >
              <h4>最新同步完成时间: {this.state.message}  </h4>
            </div>
          );
        }
      }
    
    export  default  Clock;
    

    (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。

    Github Demo


    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15423647.html

    欢迎关注我的原创高价值公众号

    上海鲜花港 - 郁金香
  • 相关阅读:
    Apache Beam的特点
    Apache Beam是什么?
    Kudu1.1.0 、 Kudu1.2.0 Kudu1.3.0的版本信息异同比较
    Kudu compaction design
    [转]Oracle trunc()函数的用法
    [转]Charts (Report Builder and SSRS)
    [转]表变量和临时表的比较
    [转]MONTHS_BETWEEN Function
    [转]Grunt 新手一日入门
    [转]Format a ui-grid grid column as currency
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15423647.html
Copyright © 2020-2023  润新知