SignalR在React/Go技术栈的实践

简介: 本文记录SignalR在react/golang 技术栈的生产小实践。

01背景


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


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


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


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


02撸起袖子干


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


c56d6df8a92657c05a4005858ae0a55b.png


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


SignalR的基本交互原理:


(1) signalR提供了一组API, 用于创建从服务端到客户端的远程过程调用(RPC),这个调用的具体体现是 :从服务端.NET 代码调用位于客户端的javascript 代码


(2) signalr提供了管理实例、连接、失连, 分组管控的API


fe3ace1cf9f0d541d4f366f9ada3df8a.png


这里面最关键的一个概念是集线器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\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), // 这是单例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={{width: '300px',float:'left',marginLeft:'10px'}} >
          <h4>最新同步完成时间: {this.state.message}  </h4>
        </div>
      );
    }
  }
export  default  Clock;


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


03效果分析


最后的效果如图:

7f2075691fc61dd3ee12d5e964b343f4.jpg



效果分析:


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


c9da15c4b315154f5a38d53eaab45557.png


以上网络交互,大部分会通过SignalR框架自动完成。

相关文章
|
4月前
|
前端开发 JavaScript 流计算
React 18 流式渲染:解锁极致性能优化实践
React 18 流式渲染:解锁极致性能优化实践
294 80
|
26天前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
158 86
|
3月前
|
前端开发 JavaScript 编译器
React编程新手入门实践教程
本书深入解析React核心思想与设计哲学,涵盖组件化思维、虚拟DOM原理及JSX本质,探讨函数组件与类组件特性,详解状态管理、生命周期控制及事件处理机制,帮助开发者掌握高效构建用户界面的技巧。
87 1
|
3月前
|
分布式计算 算法 安全
Go语言泛型-泛型约束与实践
Go语言中的泛型约束用于限制类型参数的范围,提升类型安全性。通过接口定义约束,可实现对数值类型、排序与比较等操作的支持。开发者既可使用标准库提供的预定义约束,如constraints.Ordered和constraints.Comparable,也可自定义约束以满足特定需求。泛型广泛应用于通用数据结构(如栈、队列)、算法实现(如排序、查找)及构建高效可复用的工具库,使代码更简洁灵活。
|
4月前
|
设计模式 人工智能 Go
go 依赖注入实践
依赖注入(DI)是一种软件设计模式,旨在降低代码耦合度,提高代码可测试性和可复用性。其核心思想是将依赖项从外部传入使用对象,而非由其内部创建。通过 DI,模块间关系更清晰,便于维护和扩展。常见实现包括方法注入和接口注入,适用于如 Go 等支持函数式编程和接口抽象的语言。
|
4月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:路由、中间件、参数校验
Gin框架以其极简风格、强大路由管理、灵活中间件机制及参数绑定校验系统著称。本文详解其核心功能:1) 路由管理,支持分组与路径参数;2) 中间件机制,实现全局与局部控制;3) 参数绑定,涵盖多种来源;4) 结构体绑定与字段校验,确保数据合法性;5) 自定义校验器扩展功能;6) 统一错误处理提升用户体验。Gin以清晰模块化、流程可控及自动化校验等优势,成为开发者的优选工具。
|
4月前
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
4月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:使用 Gin 快速构建 Web 服务
Gin 是一个高效、轻量级的 Go 语言 Web 框架,支持中间件机制,非常适合开发 RESTful API。本文从安装到进阶技巧全面解析 Gin 的使用:快速入门示例(Hello Gin)、定义 RESTful 用户服务(增删改查接口实现),以及推荐实践如参数校验、中间件和路由分组等。通过对比标准库 `net/http`,Gin 提供更简洁灵活的开发体验。此外,还推荐了 GORM、Viper、Zap 等配合使用的工具库,助力高效开发。
|
3月前
|
Linux Go 开发者
Go语言泛型-泛型约束与实践
《Go语言实战指南》介绍了如何使用Go进行交叉编译,即在一个操作系统上编译出适用于不同系统和架构的二进制文件。通过设置GOOS和GOARCH环境变量,开发者可轻松构建跨平台程序,无需在每个平台上单独编译。Go从1.5版本起原生支持此功能,极大提升了多平台部署效率。
|
5月前
|
设计模式 缓存 算法
Go如何进行高质量编程与性能调优实践
本文介绍了Go语言高质量编程与性能调优的实践方法。高质量编程包括良好的编码习惯(如清晰注释、命名规范)、代码风格与设计(如MVC模式)、简洁明了的代码原则,以及单元测试与代码重构的重要性。性能调优方面,涵盖算法优化、数据结构选择、I/O优化、内存管理、并行与并发处理优化及代码层面的改进。通过这些方法,可有效提升代码质量和系统性能。
108 13