# websocket
# 介绍
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当获取 Web Socket 连接后,可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
创建 WebSocket 对象:
var Socket = new WebSocket(url, [protocol])以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
# 属性
| 属性 | 描述 |
|---|---|
| Socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 |
| 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
# 事件
| 事件 | 事件处理程序 | 描述 |
|---|---|---|
| open | Socket.onopen | 连接建立时触发 |
| message | Socket.onmessage | 客户端接收服务端数据时触发 |
| error | Socket.onerror | 通信发生错误时触发 |
| close | Socket.onclose | 连接关闭时触发 |
# 方法
| 方法 | 描述 |
|---|---|
| Socket.send() | 使用连接发送数据 |
| Socket.close() | 关闭连接 |
# 数据传输
WebSocket 传输的数据都是以 Frame(帧)的形式实现的,就像 TCP/UDP 协议中的报文段 Segment。
# 连接过程
webSocket原理:利用 HTTP 请求产生握手,HTTP 头部中含有 WebSocket 协议的请求,所以握手之后,二者用 TCP 协议进行交流。
在用户使用的浏览器支持 websocket 的前提下,浏览器向服务器发送请求,并在消息头中添加 Connection: Upgrade 和 Upgrade: websocket,告诉服务器需要切换成 websoket 协议通信。当服务器支持 websocket 服务并允许客户端连接,则在响应的报文中返回 Upgrade 和 Connection 消息头域,同意浏览器使用 websocket 连接,同时返回 101 状态码表示请求还需要完成协议的切换。

发送请求
Upgrade: websocket
Connection: Upgrade
上面两个表示发起的是websocket协议
Sec-WebSocket-Key: // 一个随机的经过base64编码的字符串,像密钥一样用于服务器和客户端的握手过程
Sec-WebSocket-Protocol: // 用户定义的字符串
Sec-WebSocket-Version: // 协议版本
响应
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: // 加密后的
默认情况下,ws 协议使用 80 端口进行普通连接,加密的 TLS 连接默认使用 443 端口。
# 与 TCP/HTTP 关系
WebSocket 是基于 TCP 的独立的协议。和 HTTP 的唯一关联就是 HTTP 服务器需要发送一个“Upgrade”请求,即 101 Switching Protocol 到 HTTP 服务器,然后由服务器进行协议转换。
# 开发步骤
- 创建 socket 链接,返回一个 promise,包含注册/移除接收数据的事件、发送数据、关闭连接的方法。
async function socketIo(socketUrl) {
let ws = null
let pingTimer = null
let reconnecTimer = null
let params = null
let lockSocket = false
let hasParams = false
const listener = {}
function createSocket(socketUrl) {
try {
if ('WebSocket' in window) {
ws = new WebSocket(socketUrl)
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(socketUrl)
}
initEvents()
} catch (e) {
pingTimer && clearInterval(pingTimer)
lockSocket = true
reconnect(socketUrl)
}
}
// 创建socket连接
await createSocket(socketUrl)
return new Promise((resolve, reject) => {
const data = {
close: () => {
ws.close()
},
addListener: (key, callback) => {
listener[key] = callback
},
send: data => {
params = data || ''
hasParams = true
sendMessage(data)
},
removeListener: key => {
delete listener[key]
}
}
resolve(data)
})
}
- 初始事件,包括打开连接 open、接收数据 message、错误 error 和关闭 close 事件。
const initEvents = () => {
ws.onopen = () => {
console.log('\x1B[32m%s\x1b[0m', '---socket已连接---')
lockSocket = false
pingTimer && clearInterval(pingTimer)
sendPing()
if (ws.readyState === 1 && hasParams) {
sendMessage(params)
}
}
ws.onmessage = event => {
const data = event.data
Object.keys(listener).forEach(k => {
if (typeof listener[k] === 'function') {
listener[k](data)
}
})
}
ws.onerror = event => {
console.log('\x1B[31m%s\x1b[0m', '---socket连接发生错误---')
console.log(event)
pingTimer && clearInterval(pingTimer)
lockSocket = true
reconnect(socketUrl)
}
ws.onclose = () => {
console.log('\x1B[33m%s\x1b[0m', '---socket连接关闭---')
pingTimer && clearInterval(pingTimer)
lockSocket = true
reconnect(socketUrl)
ws.close()
}
}
- 当成功连接 socket 后,就可以进行通信了
const sendMessage = data => {
if (!ws || ws.readyState !== 1) return
ws.send(data)
}
- 当 socket 突然断开,我们要有重连机制,确保因网络等各种因素导致连接断开而无法实时通信问题。
const reconnect = socketUrl => {
if (!lockSocket) return
if (reconnecTimer) clearTimeout(reconnecTimer)
lockSocket = true
reconnecTimer = setTimeout(() => {
createSocket(socketUrl)
}, 5000)
}
- 在与服务端正常通信后,就可以接收相应数据了。为了确保这个连接一直在,每隔一段时间发送一个心跳标识,服务端检测到该标志,说明该连接仍在使用。如果隔断时间没接收到,自动关闭连接,不再往客户端推送数据。
const sendPing = () => {
pingTimer = setInterval(() => {
sendMessage('123')
}, 40000)
}
- 注册 socket
socketIo('ws://xxx').then(res => {
Vue.prototype.$socket = res
})
- 添加接收数据事件
export default {
mounted() {
this.$socket.addListener('getRealTime', this.getRealTime)
},
methods: {
getRealTime(data) {
console.log(data)
}
}
}