如何创建一个稳定的WebSocket连接?

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

什么是全双工

  1. 全双工以太网使用两对电缆线,半双工以太网使用一对电缆线。
  2. 全双工方式采取点到点的连接,可以得到更高的数据传输速度

image.png

WebSocket协议用来实现全双工通信,是TCP/IP协议族的子集,属于应用层协议

WebSocket.readyState

  1. WebSocket.CONNECTING 连接中,状态码为0,也就是new WebSocket(url)的执行开始
  2. WebSocket.OPEN 连接成功,状态码为1,也就是监听到open事件
  3. WebSocket.CLOSING 关闭中,状态码为2,可能由服务器触发,也可能由客户端触发
  4. WebSocket.CLOSED 已关闭,状态码为3,只有连接状态下,socket才能send发送消息,其他情况下会报错。
graph TD
ws://ip:port --> 客户端发送http请求 --> 头部Connection:Upgrade被服务器识别 --> 切换到WebSocket协议 --> 创建WebSocket实例

WebSocket原型方法

  • close readyState状态由2变3
  • send 入参可以为StringArrayBufferBlob

事件

  • open     【连接成功】回传数据结构{data}
  • message【接收到数据{data}
  • close     【连接关闭{ wasClean,data,code,reason} wasClean含义是各种资源是否得到释放,code则是关闭的状态码,reason则是关闭的原因。
  • error     【连接报错{data}

单例模式和心跳包

  • 所谓单例模式就是实例有且仅有一个实例存在。
  • 一个应用全局最好只有一个WebSocket连接,同时保证它的唯一码的唯一性。
  • 由于WebSocket连接不稳定,会每隔一段时间询问服务器连接是否断掉,就像心跳一样,所以叫心跳包。

对WebSocket的封装

class SingleSocket {
    _socket
    _uuid = this.genUUID()
    lastData
    constructor(options = {}) {
        this.options = options
    }
    send(options) {
        const socket = this._getSingleInstance()
        const { data } = options
        socket.send(data)
    }
    _genUUID() {
      return 'uuid'
    }
    _bindEvent(socket) {
        socket.addEventListener('open', this.open)
        socket.addEventListener('message', this.message)
        socket.addEventListener('close', this.close)
        socket.addEventListener('error', this.error)
        setInterval(this._getSingleInstance, 1200)
    }
    ...
    _close(options) {
        this._getSingleInstance()
    }
    _error(options) {
        this._getSingleInstance()
    }
    ...
    _getSingleInstance() {
        if (this._socket && !this._socket.readyState === WebSocket.CLOSED) {
            return this._socket
        }
        this._socket = null
        const { url } = this.options
        this._socket = new WebSocket(this._addParam(url, `&id=${this._uuid}`))
        this._bindEvent(this._socket)
        return this._socket
    }
}
复制代码
  • 每一个客户端的socket连接都是唯一的,通过传递uuid,可以让服务端更好维护socket连接池,id是与后台协商传递的参数,你可以传任何参数,只要后端能接收并处理。
  • 每一个客户端WebSocket的实例是单例的,getSingleInstance的逻辑是,如果存在this._socket并且_socket的状态不是已关闭,则返回这个socket。否则,就创建一个WebSocket实例。并绑定对应的时间。
  • bindEvent方法中,有一个定时器,这个就是所谓的心跳机制。
  • 并且在close和error方法中进行重连。这一系列的动作就是为了确保WebSocket的稳定连接。
  • 我们给外部暴露了send方法,用于想服务端发送数据,之所以要封装一层,就是为了确保拿到的socket实例是全局唯一的。
  • 我们定义了lastData属性,用来记录上一次服务器发来的数据,这样便于历史追踪。
  • 构造函数的options选项,url是必填,我们可以做一个必填校验。options还可以传入其它如接收到消息的回调,这样我们可以对应修改视图。
  • addParam方法向url中注入id时间参数,以便标识连接的时间节点。

socket.io是一个优秀的实时通信库,它包括了前端到后台,涵盖各种语言的一整套体系,当浏览器不支持WebSocket协议时,可以自动降级到轮询机制。有兴趣的读者看这里。

WebSocket是一套前后台通讯的协议,单纯的长连接并不属于全双工通信。服务器可以根据WebSocket协议规范去实现不同版本的WebSocket,socket.io就是自己实现了一套WebSocket协议,同时向老旧的浏览器做了兼容的一套前后台方案。

SingleSocket这套代码只是一个简单的版本,读者想要更加稳定强大的功能,可以在这个基础上去扩展。

当不可避免的WebSocket失去连接,比如网络失去连接,应用关闭,手机关机。客户端每次接收到服务端的数据,可向服务器发送一次确认包。如果没有收到,则可以将数据包缓存起来,使用数据库或者消息队列或者redis都可以。在指定客户Socket连接时,将未发送的数据一并发送即可。

猜你喜欢

转载自juejin.im/post/7032243206687293448