本文主要以前端的视角来讲解Websocket相关内容,重点是WebSocket的设计过程以及设计思路,文章的思路适用于前端全领域,不管是pc端也好,移动端也罢,甚至uni-app、小程序,实现思路都是一样的。
为什么我要写这一篇文章?因为目前我发现技术圈里写的WebSocket相关文章,都太基础了,太入门了,只是简单的教你怎么用,却没有说该如何做到好用,如何去进行设计,我觉得这才是我们学习的重点。
本篇文章主讲设计过程及思路,至于如何将设计转化为实现,我后续将在下一篇文章中写出。
设计图
交互设计
前后端统一数据体:
// 请求体 |
字段 | 非空 | 释义 | 作用 |
---|---|---|---|
msgId | √ | socket处理事项 | |
uniqueId | √ | 消息唯一id | 前端实现消息匹配机制,每条socket消息都会携带,后端需原样传回给前端 |
code | √ | 后端响应状态码 | 200代表成功 |
message | 后端响应提示信息 | ||
data | 前端/后端传递的数据体 |
心跳设计
前端定时推送一条消息给后端,后端收到后也推送一条回复,当一定时间内,其中一方没有给予回复时,则认为已失联,需断开并重新建立连接
字段设计:
lastRequestTime:最后请求时间
lastResponseTime:最后回复时间
failCount:失败次数
maxFailCount:最大失败次数
interval:心跳间隔时间(ms)
timeout:超时时间(ms)
timer:setTimeout定时器对象
实现逻辑:
前端发送心跳,并记录本次心跳的发送时间戳
后端回复心跳,前端收到消息后记录本次回复的时间戳
假如
lastResponseTime - lastRequestTime > timeout
,则failCount +1
假如
failCount > maxFailCount
,则不再发送心跳,直接执行socket重连socket重连时,会清除心跳定时器
socket重连成功时,
failCount、lastRequestTime、lastResponseTime 归零
,并开启心跳
消息码:
客户端request:100201
服务端response:100200
重连设计
当通信双方有一方出现异常后,需要重新建立新的socket连接,可能出现异常断联的情况如下:
客户端断网
心跳超时
服务端异常
正常情况下,只要出现上述异常情况,socket消息都无法发出,客户端要能够捕获到上述情况,并作出重连处理
字段设计:
lock:重连锁
interval:重连的时间间隔
timer:setTimeout定时器对象
实现逻辑:
客户端检测到异常,判断重连锁是否为true,如果为true则退出(为true代表socket正在重连,避免客户端多次调用重连方法的情况)
清除定时器对象,避免出现定时器堆积而引发其他问题
关闭现有的socket连接
开启重连锁,并建立新的连接
假如重连失败,一定时间后再次调用重连,回到第1步
重连成功,关闭重连锁
重点:
- 重连锁的状态控制
设计图:
插件系统
插件系统是我们在socket里实现的一种功能机制,其主要作用在于,当socket进入某种状态时,会通知插件,并向插件传递相关数据
插件属于权利的顶点,可以控制socket实例的任何参数及行为,并可拓展出其他能力
生命周期
插件系统存在以下几个生命周期,每当socket触发生命周期方法,会将其生命周期类型和相应数据,传递给插件系统
open:socket建立连接成功时
close:socket关闭连接时
request:socket向服务端发送消息时
response:socket向客户端推送消息时
其中,open和close在一个连接内只会触发1次,而request和response可能会触发多次
插件本质
插件的本质就是一个函数,其类型定义如下:
export type TPluginParams = { |
注意事项:
open 和 close 的 data参数为 undefined
插件内无法使用 this,可采用第三个参数 socket 代替
使用场景:
日志搜集
开发调试
数据加工及转发
……
消费者机制
消费者机制主要用于前端实现消息匹配机制
痛点情景
前端中,假如函数A和函数B,同时发出了一个msg=1000的消息,那么,此时服务端就会推送2条一模一样的响应回来
此时,我们如何知道哪条数据是函数A的,哪条数据是函数B的?
解决问题
消费者机制除了解决上述场景的问题之外,还实现了消息发起者在得到服务端的响应后,再去处理其它事情
也就是实现了 请求与响应的唯一性与匹配
消费者本质
消费者,本质上就是一个函数,这个函数负责去消费指定的服务端响应
大白话就是,消费者一定会接收到指定的服务端响应,当拿到响应后,消费者接下来可以去干嘛
类型定义如下:
export type TConsumers = (data: unknown) => void |
匹配机制
如何实现一个消费者,有 **针对性 **的去消费指定的响应?
生命周期
一个消费者的生命周期只有一次,也就是单次回话
当一个消费者收到响应并去执行后,其就从消费者集合表中移除,因为每一个消费者都是唯一的,消费者、客户端请求、服务端响应属于1对1关系
监视者机制
概述
本质上,监视者就是一个函数,这个函数的作用就是监听、监视某些状态,那具体它监视什么状态呢?Websocket的响应数据
当服务端推送一条消息至客户端时,会被监视者捕获到,并且会将socket响应的消息传递给监视者
监视者VS消费者
监视者与消费者的作用是一样的,区别在于:
消费者:socket响应与消费者属于1:1关系
监视者:socket响应与监视者属于1:n关系,也就是,一个socket响应,可能会被多个监视者捕获到并进行处理
应用场景
当我们发送一条消息后,希望能在准确的得到这条消息的回复后,做其它处理时,可采用消费者
当我们推送一条消息后,不关心服务端什么时候会回复,也不关心请求与响应的匹配性,可采用监视者
服务端主动推送消息的时候,需要用监视者来监听
设计图
消息队列
消息队列的作用在于socket发生异常时,暂存所有待发送的消息,等socket恢复正常时,再将消息队列里的消息推送给服务端
进入队列的条件:
连接断开
心跳超时
连接异常
重连中
实现逻辑:
客户端发送消息时,先检查socket是否正常,若正常则直接发送,否则push至消息队列
当连接恢复正常时,延迟300ms后,一次性取出整条队列,循环遍历并执行发送
若在遍历发送的过程中突然出现异常,则立即push回到队列中
若在遍历发送过程中又有新的消息入队,则在执行本条队列完毕后,重新执行第2步
每次出现异常均重新执行以上步骤
第2点延迟300毫秒是因为在这期间可能还有新的消息会入队,等待一下
设计图: