「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」
什么是全双工
- 全双工以太网使用两对电缆线,半双工以太网使用一对电缆线。
- 全双工方式采取点到点的连接,可以得到更高的数据传输速度。
WebSocket协议用来实现全双工通信,是TCP/IP协议族的子集,属于应用层协议
WebSocket.readyState
WebSocket.CONNECTING
连接中,状态码为0,也就是new WebSocket(url)的执行开始WebSocket.OPEN
连接成功,状态码为1,也就是监听到open事件WebSocket.CLOSING
关闭中,状态码为2,可能由服务器触发,也可能由客户端触发WebSocket.CLOSED
已关闭,状态码为3,只有连接状态下,socket才能send发送消息,其他情况下会报错。
graph TD
ws://ip:port --> 客户端发送http请求 --> 头部Connection:Upgrade被服务器识别 --> 切换到WebSocket协议 --> 创建WebSocket实例
WebSocket原型方法
- close
readyState
状态由2变3 - send 入参可以为
String
,ArrayBuffer
或Blob
事件
- 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连接时,将未发送的数据一并发送即可。