心跳机制解决webSocket自动断开连接的问题

1. 事发经过

出现这个问题源于我在写斗地主HTML前端时,发现当客户端与服务器(通过webSocket)没有数据交互时,经过大概45 ~ 60s后会自动断开连接,通过在onclose绑定的方法中监听到断开连接的事件:

1
2
3
this.websocket.onclose = function () {
console.log('websocket断开连接了 ...');
}

这可给咱愁坏了,因为我写的这个客户端所有的功能都依靠webSocket来实现,断开连接后意味着无法通知玩家加入房间、准备、游戏开始….等所有游戏的功能。

在谷歌的时候,我忽然想起了之前在用Netty开发Websocket服务器时有一个心跳的功能,该功能可以用来确保客户端仍然保持连接的状态。找到了问题出现的原因,那么就非常好解决了。

2. 解决过程

第一个想到的方法是在监听到断开时重新创建连接,但是考虑到重新连接浪费资源的情况下,我决定还是使用心跳来解决。下面是关于WebSocket心跳机制的介绍:

在经过握手之后的任意时刻里,无论客户端还是服务端都可以选择发送一个ping给另一方。 当ping消息收到的时候,接受的一方必须尽快回复一个pong消息。 例如,可以使用这种方式来确保客户端还是连接状态。

2.1 前端

因为我的客户端是用Vue写的,这里只针对Vue的写法。做法很简单,第一步,只需要在成功连接后开始一个setTimeout的延时事件,在30s(或者自定义时间)后向服务器发送一个ping消息。

第二步,在接收到服务器响应的pong消息时,调用resetWsTimeOut重置延时事件,等待30s后重新发送ping消息,周而复始:

1
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
export default {
data() {
return {
timeout: 30000, // 30s
websocket: null,
wsTimeoutObj: null
}
},
methods: {
startWsTimeOut() { // 开始计时器
let self = this;
this.wsTimeoutObj = setTimeout(function () {
self.websocket.send("ping"); // 发送给服务器ping消息
}, self.timeout)
},
resetWsTimeOut() { // 重置计时器
clearTimeout(this.wsTimeoutObj); // 清空计时器
this.startWsTimeOut();
},
onWsMessage(event) { // 处理websocket接收消息事件
if (event.data === 'pong') {
this.resetWsTimeOut();
}
}
},
create() {
let self = this;
if ('WebSocket' in window) {
this.websocket = new WebSocket("ws://api.exmaple.com/ws");
}
this.websocket.onmessage = this.onWsMessage;
this.websocket.onopen = function () { // 连接成功回调
self.startWsTimeOut();
}
}
}

这样,就完成了心跳机制的前端实现,因为和服务器有ping/pong消息的传送,就不会导致没有消息交互而自动断开连接。

2.2 后端

由于后端我采用的是Spring高度封装的web.socket包,所以只需要在重写的handleMessage方法中接收客户端发送的消息即可。

逻辑很简单,在接受到客户端发送的ping消息后,回复一个pong消息,达到心跳检测的功能:

1
2
3
4
5
6
7
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> message) throws Exception {
String payload = (String) message.getPayload();
if (payload.equals("ping")) {
webSocketSession.sendMessage(new TextMessage("pong"));
}
}
Pushy wechat
欢迎订阅我的微信公众号