目录

梗概

基本做法

实操部分

后端实现

前端websocket客户端

总结


梗概

上章实现了后端deployment map的实时更新,我们还需要把这部分的更新传送到前端。

本章重点讲述前后端通过websocket进行这部分的数据交互。

基本做法

基本思路是,当前端请求后端列表api时,同时发送另一个http请求到后端,根据websocket的原理,可以将该连接升级成websocket连接。我们将这个客户端存到一个本地缓存维护的map中,当资源发生变动,触发handler,我们同样将更新后的列表发送到前端,前端更新用于列表展示的那部分数据。

实操部分

后端实现

首先定义接口,http升级websocket,并将客户端保存在map里面

func Connect(c *gin.Context) string {
	client,_ := websocket.Upgrader{}.Upgrade(c.Writer, c.Request, nil) //升级成websocket
	wscore.ClientMap.Store(client)
	return "true"
}

其中,ClientMap是定义好的一个全局对象,供所有需要使用websocket的handler使用

var ClientMap *ClientMapStruct

type ClientMapStruct struct {
	data sync.Map //  key 是客户端IP  value 就是 WsClient连接对象
	lock sync.Mutex
}

那么,针对前步中升级websocket获得的连接对象,我们需要有以下操作:

1.存入到map中

this.data.Store(conn.RemoteAddr().String(), wsClient)

2.开启协程定时发送消息检测存活,ping一下,出错就从map中删除这个连接,这是比较初级的做法。

func (this *WsClient) Ping(waittime time.Duration) {
	for {
		time.Sleep(waittime)
		func() {
			this.Locker.Lock()
			defer this.Locker.Unlock()
			err := this.conn.WriteMessage(websocket.TextMessage, []byte("ping"))
			if err != nil {
				ClientMap.Remove(this.conn)
				return
			}
		}()

	}
}

这里定义了一个结构体,并加入了sync.Mutex。因为websocket不支持并发写,所以加入互斥锁。

题外话,其实websocket也会存在客户端继续压入数据的情况。对此,需要开启一个协程,反复(死循环)从连接中读取信息,并推入这部分信息到channel中。再另外开启一个协程去处理 总控 循环。这样是比较合理的做法,这与我们要实现的功能无关,所以不做过多的演示。

然后,我们需要实现一个传参为interface{}的方法去接受一个对象,并把这部分的对象数据给发送到所有的客户端,客户端判断数据是否和我需要的类型相匹配,(如果匹配)做对应更新。这同样是比较初级的做法,有如下。

//向所有客户端 发送消息--发送资源列表
func (this *ClientMapStruct) SendAll(v interface{}) {
	this.data.Range(func(key, value any) bool {
		func() {
			c := value.(*WsClient).conn
			value.(*WsClient).Locker.Lock()
			defer value.(*WsClient).Locker.Unlock()
			err := c.WriteJSON(v)
			if err != nil {
				this.Remove(c)
				log.Println(err)
			}
		}()

		return true
	})
}

然后在informer-handler中调用该接口,包括了新增、删除、更新,下面是一个示例。

func (this *DepHandler) OnAdd(obj interface{}) {
	this.DepMap.Add(obj.(*v1.Deployment))
	ns := obj.(*v1.Deployment).Namespace
	//向所有的ws客户端发送消息
	//然后前端取消息内容里面判断是否需要更新当前页面
	wscore.ClientMap.SendAll(gin.H{
		"type": "deployment",
		"result": gin.H{
			"ns":   ns,
			"data": this.DepService.ListAll(ns),//这里是你从map中获取到对应namespace下的所有deploy
		},
	})
}

至此,后端的websocket改造已实现。

接下来我们看前端。

前端websocket客户端

首先引入js中websocket客户端的实现:

let lock=false;
let wsClient;
function reConnect() {
    if (lock) return;
    lock = true;
    console.log("正在重连")
    setTimeout(function() {
        NewClient();
        lock = false;
    }, 2000);
}
//0 - 表示连接尚未建立 1 - 表示连接已建立,可以进行通信。 2 - 表示连接正在进行关闭 3 - 表示连接已经关闭或者连接不能打开
const GetClient=function () {
    if(wsClient!=null && wsClient.readyState===1){
        return wsClient
    }
    NewClient()
    return wsClient
}
const NewClient=function () {
    wsClient = new WebSocket("ws://localhost:8080/ws");
    wsClient.onopen = function(){
        console.log("open");
    }
    wsClient.onclose = function(e){
        console.log("close");
        reConnect()
    }
    wsClient.onerror = function(e){
        console.log(e);
        reConnect()
    }
    return wsClient
}

export {NewClient}

 其中,指定了后端http的接口,后端在接受这个请求后会将连接升级成websocket。

然后在列表组件中引入

import { NewClient } from '@/utils/ws'

接着在create函数中调用websocket客户端:

export default {
  data() {
    return {
      list: null,
      listLoading: true,
      wsClient: null,
    }
  },
  created() {
    this.fetchData()
  },
  methods: {
    fetchData() {
      this.listLoading = true
      this.wsClient = NewClient()
      this.wsClient.onmessage=(e)=>{
        if (e.data !== 'ping') {
            const obj = JSON.parse(e.data)
            if (obj.type === 'deployment') {
              this.list = obj.result.data
              //列表更新无法重新渲染页面,需要手动更新
              this.$forceUpdate()
            }
          
        }
      }
    },
  }
}
</script>

 其中,消息内容为ping的用于探活的测试数据会被drop掉,判断类型为‘deployment’的数据则会被更新到data中。要注意的是,列表数据的更新必须调用forceUpdate来进行重新渲染。

总结

至此,整一个websocket的前后端调用就完成了。可见的是,这种是比较初级的做法,未来将会看见更优雅的实现。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐