bug 现象描述

Socket.IO 客户端有个奇葩的 bug,详见 Multiple sockets open after reconnect #430

其实很早就发现这个问题了,客户端监听的事件会多次收到响应,具体表现为:

  • 第 1 次断线重连后,收到每条数据都会触发 2 次 callback
  • 第 2 次断线重连后,收到每条数据都会触发 3 次 callback
  • 第 3 次断线重连后,收到每条数据都会触发 4 次 callback

很明显是个 bug。

之前解决的方式是使用业务方式:即在收到数据后做去重处理,并没有去深究 Socket.IO 的内部原因(当时确实没有心思去研究)。

今天在制作 demo 页面时,此现象引发了我的关注,特地去检查了 Socket.IO 的全部 issues,终于发现了类似 #430 等多个相同故障的 issues,且早在六年前就已经出现这个问题了,因此我更加确定这不是个案。

详细阅读过相关的所有 issues 后,网友们并没有给出很好的解决办法。但是看了广大网友的描述后,心中有了一个思路。

经过排查,确定不是服务端发送数据的问题(即:并不是服务端 emit 了多次相同的数据),而是客户端监听方法(Socket.on())被多次触发导致。

这更证实了我先前的思路:on 方法被执行多次,那么给 on 方法传入的 callback 方法被绑定了多次,结果导致多次触发

这个结论与前面多次断线重连后的现象规律是吻合的!

bug 成因分析

  1. HTML 页面 loaded,Socket.IO 类库对应的业务代码开始运行
  2. Socket.IO 类库运行 io 方法连接 WebSocket 服务端(此方法返回 Socket 对象),并开始监听 connect 事件
  3. 连接成功后,触发 connect 事件,Socket.on 事件监听绑定 callback 完成
  4. 运行其他业务方法。。。
  5. 可能网络不稳定、可能是被服务端主动断开、可能是浏览器机制等,长连接断开
  6. Socket.IO 类库发现连接被断开,尝试重新连接,客户端触发 reconnect 事件
  7. Socket.IO 类库重连成功,再次触发 connect 事件,导致步骤 3 再次运行。。。(故障源定位成功~)

解决方案

发现了问题根源,剩下的就是 fixed 了~

在业务方法中使用了一个开关变量来记录 Socket 是否已经绑定过事件监听,如果绑定过就跳过,否则才会绑定。

之前的业务方法

1
2
3
4
5
6
let socket = io('http://websocket server address');
socket.on('connect', () => {
socket.on('news', (data) => {
console.log(data);
});
});

之后的业务方法

1
2
3
4
5
6
7
8
9
10
let bind = false; // 标记 callback 是否已经绑定
let socket = io('http://websocket server address');
socket.on('connect', () => {
if (bind === false) {
socket.on('news', (data) => {
console.log(data);
});
bind = true;
};
});

只要避免多次给 Socket 对象绑定 callback,即可解决问题。

注:荆秀网官网 JS 类库已经更新,参见:#L32

done~