WebSocket协议
# WebSocket由来
通信只能由客户端发起,不具备服务器推送能力。
应用场景:即时聊天通信、多玩家游戏、在线协同编辑/编辑、实时数据流的拉取与推送、体育/游戏实况、实时地图位置;
实时更新或连续数据流,则可以使用WebSocket;获取旧数据或只获取一次数据,则用HTTP协议;
# WebSocket 与 HTTP 的区别
相同点: 都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。
联系: WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。
不同点:1、WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息,而HTTP是单向的, HTTP1.1 中有一个 keep-alive,在一个 HTTP 连接中,可以发送多个 Request接收多个 Response,但是 一个 Request 只能有一个 Response 2、WebSocket是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。
注意:虽然HTTP/2也具备服务器推送功能,但HTTP/2 只能推送静态资源,无法推送指定的信息。
# Websocket的优缺点
优点:WebSocket协议一旦建议后,互相沟通所消耗的请求头是很小的;服务器可以向客户端推送消息了 缺点:少部分浏览器不支持,浏览器支持的程度与方式有区别(IE10)
# WebSocket连接的过程
首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等; 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据; 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。
- 客户端发起协议升级请求
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
2
3
4
5
WebSocket复用了HTTP的握手通道。--- 客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。
Connection: Upgrade
:表示要升级协议
Upgrade: websocket
:表示要升级到websocket协议。
Sec-WebSocket-Version: 13
:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Version header,里面包含服务端支持的版本号。
Sec-WebSocket-Key
:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
服务端:响应协议升级
状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
2
3
4
# websocket 断线重连
- 判断在线离线?
当客户端第一次发送请求至服务端时会携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果不存在就存入db或者缓存中, 第二次客户端定时再次发送请求依旧携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果存在就把上次的时间戳拿取出来,使用当前时间戳减去上次的时间,得出的毫秒秒数判断是否大于指定的时间,若小于的话就是在线,否则就是离线;
怎么断线?
- 主动断开连接,
ws.close();
- 主动断开连接,
断线原因
- websocket超时没有消息自动断开连接;【 超时 】
解决: 在小于服务端设置的超时时间内发送心跳包,有2中方案: 客户端主动发送上行心跳包,或者服务端主动发送下行心跳包。
- websocket异常:服务端出现中断,交互切屏等客户端异常中断;【 中断 】
解决:客户端通过onclose 关闭连接,服务端再次上线时则需要清除之间存的数据,若不清除则会造成只要请求到服务端的都会被视为离线。
措施:
异常中断 ---> 重连:引入reconnecting-websocket.min.js,ws建立链接方法使用js库api方法
断网监测 ----> 使用js库:offline.min.js
# 心跳包机制
下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
- 客户端每隔一个时间间隔发生一个探测包给服务器
- 客户端发包时启动一个超时定时器
- 服务器端接收到检测包,应该回应一个包
- 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
- 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
# 客户端的 API
新建WebSocket实例
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8080');
- webSocket.readyState 当前状态
switch (ws.readyState) {
case WebSocket.CONNECTING: // CONNECTING:值为0,表示正在连接
case WebSocket.OPEN: // 值为1,表示连接成功,可以通信了
case WebSocket.CLOSING: // 值为2,表示连接正在关闭
case WebSocket.CLOSED: // 值为3,表示连接已经关闭,或者打开连接失败
}
2
3
4
5
6
webSocket.onopen --- 连接成功后的回调函数
webSocket.onclose --- 连接关闭后的回调函数
webSocket.onmessage --- 收到服务器数据后的回调函数
webSocket.send() --- 向服务器发送数据
webSocket.bufferedAmount --- 表示还有多少字节的二进制数据没有发送。用来判断发送是否结束
webSocket.onerror --- 报错时的回调函数
# 服务端实现
Node 实现有三种: µWebSockets、Socket.IO、WebSocket-Node
websocketd --port=8080 bash ./counter.sh
启动一个 WebSocket 服务器,端口是8080。每当客户端连接这个服务器,就会执行counter.sh脚本,并将它的输出推送给客户端.
// 客户端的 JavaScript 代码
var ws = new WebSocket('ws://localhost:8080/');
ws.onmessage = function(event) {
console.log(event.data);
};
2
3
4
5
# 在vue项目中使用websocket
<template>
<div class="box"> websocket </div>
</template>
<script>
const heartCheck = {
timeout: 60 * 1000,
timer: null,
serverTimer: null,
reset() {
this.timer && clearTimeout(this.timer)
this.serverTimer && clearTimeout(this.serverTimer)
},
start(ws) {
this.reset()
this.timer = setTimeout(() => {
// onmessage拿到返回的心跳就说明连接正常
ws.send(JSON.stringify({ heart: 1 }))
// 如果超过一定时间还没响应(响应后触发重置),说明后端断开了
this.serverTimer = setTimeout(() => { ws.close() }, this.timeout)
}, this.timeout)
}
}
export default {
name: 'Websocket',
data() {
return {
wsuri: 'ws://123.207.167.163:9010/ajaxchattest', // ws wss
lockReconnect: false, // 连接失败不进行重连
maxReconnect: 5, // 最大重连次数,若连接失败
socket: null
}
},
mounted() { this.initWebSocket() },
methods: {
reconnect() {
console.log('尝试重连')
if (this.lockReconnect || this.maxReconnect <= 0) { return }
setTimeout(() => {
// this.maxReconnect-- // 不做限制 连不上一直重连
this.initWebSocket()
}, 60 * 1000)
},
initWebSocket() {
try {
if ('WebSocket' in window) { this.socket = new WebSocket(this.wsuri) }
else { console.log('您的浏览器不支持websocket') }
this.socket.onopen = this.websocketonopen
this.socket.onerror = this.websocketonerror
this.socket.onmessage = this.websocketonmessage
this.socket.onclose = this.websocketclose
} catch (e) { this.reconnect() }
},
websocketonopen() {
console.log('WebSocket连接成功', this.socket.readyState)
heartCheck.start(this.socket)
// this.socket.send('发送数据')
this.websocketsend()
},
websocketonerror(e) {
console.log('WebSocket连接发生错误', e)
this.reconnect()
},
websocketonmessage(e) {
let data = JSON.parse(e.data)
console.log('得到响应', data, '可以渲染网页数据...')
// 消息获取成功,重置心跳
heartCheck.start(this.socket)
},
websocketclose(e) {
console.log('connection closed (' + e.code + ')')
this.reconnect()
},
websocketsend() {
let data = { id: 'a1b2c3' }
this.socket.send(JSON.stringify(data))
}
},
destroyed() { this.socket.close() }
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80