SignalR在React/Go技术栈的实践

news/2024/7/10 3:25:45 标签: java, vue, react, javascript, js
js_content">

a9ef8dc4cddf140bb4e5534aa96f5d63.gif

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

01

背景

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

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

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

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

02

撸起袖子干

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

c847660d080ca7f1a2f1dbb176fd9df4.png

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

SignalR的基本交互原理:

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

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

3e0fb85fd153f2638e295f2680b8e5aa.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

效果分析

最后的效果如图:

c48e16b6830b8741fb21dff9669b1332.gif

效果分析:

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

d6ad7ff920b8195e89b476dbe47b886b.png

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

源码:Github Demo[2]

5aba3df56c29737e9144da45dc785a37.png

引用链接

[1] Github仓库: https://github.com/philippseith/signalr
[2] Github Demo: https://github.com/zaozaoniao/SignalR-apply-to-react-and-golang

123677199f5248603c64ac3c036c4423.gif

●实时通信技术大乱斗

●.NET WebSocket 核心原理初体验

●.NET gRPC核心功能初体验

●大前端快闪四:这次使用一个舒服的姿势插入HttpClient拦截器

●大前端快闪三:多环境灵活配置react

●大前端快闪二:react开发模式 一键启动多个服务

●大前端快闪:package.json文件知多少?

“赞”d395f0fc7e374860e07f664a47e04673.gif“在看”30d40fa676c1fca9c289fee2297d86ec.gif

体现态度很有必要!


http://www.niftyadmin.cn/n/1864468.html

相关文章

SingnalR 开发到生产部署闭坑指南

前天倒腾了一份[SignalR在react/go技术栈的实践01SignalR默认要协商传输方式SignalR 默认要求协商传输方式[1]不管是.NET客户端还是JavaScript客户端&#xff0c;构建连接时都存在一个默认配置&#xff1a;SkipNegotiationfasle&#xff0c;负负得正就等于要求协商&#xff0c;…

Gitflow branch与Docker image tag命名冲突怎么办?

谷歌还是比必应要好用一点。在前公司&#xff0c;我根据主流的git flow 给团队搭建了一套devops流程&#xff0c;运行在 docker & k8s上。在现代devops流程中&#xff0c;一般推荐使用git分支名或者git tag作为镜像的tag名。在实际操作中&#xff0c; 我遇到了一个流程阻塞…

Hostonly cookie是什么鬼?

点击上方蓝字关注我们吧知道cookie hostonly属性的请举手&#x1f9d0;01Cookie常见姿势、疑难梳理目前w3c定义浏览器存放每个cookie需要包含以下字段&#xff1a;cookie属性基本描述举例备注namevaluecookie键值对ida3fWaexpirescookie过期时间expiresTue, 10-Jul-2013 08:30:…

HTTP1.1 Keep-Alive到底算不算长连接?

✎ 码甲说 在基础架构部浸润了半年&#xff0c;有一些认知刷新想和童靴们交代一下&#xff0c; 不一定全面&#xff0c;仅代表此时的认知&#xff0c; 也欢迎筒靴们提出看法。本文聊一聊口嗨用语&#xff1a;“长连接、短连接”&#xff0c; 文章会按照下面的思维导图来讲述&…

域名可以运行了!

之前跟丹雅要的那个域名&#xff0c;终于可以正常运行了。简单的记录一下从申请到开通的过程&#xff0c;以便之后查阅。 1&#xff0c;首先去申请你的域名&#xff0c;当然缴费是必然的了。提供域名注册服务的网站很多&#xff0c;万维啊什么的。我是不劳而获&#xff0c;所以…

宝藏好物gRPCurl

gRPCurl简介gRPCurl[1]是一个与gRPC服务器交互的命令行工具&#xff0c;可认为是gRPC的curl工具。gRPCurl用于从命令行调用gRPC服务器支持的RPC方法&#xff0c;gRPC使用二进制编码(protobuf), 不能利用常规的curl工具(早期的curl版本还不支持HTTP/2)。1. gRPCurl工具接受json编…

写一个属于自己的模板引擎(2)

接上篇&#xff08;1&#xff09; 新建文件stupid_parser.class.php。 先定义我们的类StupidParser&#xff1a; <?php class StupidParser { } 我们这个只要一个成员变量就可以了&#xff0c;就是$template&#xff0c;他是用来保存模板的内容的。<?php class Stup…

有关[Http持久连接]的一切,卷给你看

上文中我的结论是&#xff1a; HTTP Keep-Alive 是在应用层对TCP连接进行滑动续约复用&#xff0c; 如果客户端/服务器稳定续约&#xff0c;就成了名副其实的长连接。目前所有的Http网络库都默认开启了HTTP Keep-Alive&#xff0c;今天我们从底层TCP连接和排障角度撕碎HTTP持久…