Node.js Socket.io 页面刷新多个连接

21

我有一个使用socket.io (1.5)的简单node.js服务器代码:

var io = require('socket.io').listen(8080);

io.on('connection', function(socket) {

    console.log(' %s sockets connected', io.engine.clientsCount);

    socket.on('disconnect', function() {
        console.log("disconnect: ", socket.id);
    });
});

如果我运行这段代码并按 F5 键多次,在某些情况下,新连接可能会在旧连接断开之前创建。经过一段时间后,我认为这是由于心跳超时,所有的连接都将被关闭。看到结果:

 2 sockets connected
 3 sockets connected
 4 sockets connected
 5 sockets connected
 6 sockets connected
 7 sockets connected
 8 sockets connected
 9 sockets connected
 10 sockets connected
 11 sockets connected
disconnect:  0h_9pkbAaE3ftKT9AAAL
 11 sockets connected
 12 sockets connected
 13 sockets connected
 14 sockets connected
disconnect:  oB4HQRCOY1UIvvZkAAAP
 14 sockets connected
 15 sockets connected
disconnect:  LiIN0oDVoqbePgxFAAAR
 15 sockets connected
 16 sockets connected
 17 sockets connected
 18 sockets connected
disconnect:  zxvk-uhWABHzmu1uAAAV
 18 sockets connected
 19 sockets connected
 20 sockets connected
disconnect:  FlboxgTzcjf6ScffAAAY
 20 sockets connected
 21 sockets connected
disconnect:  9UGXbnzukfGX_UtWAAAa
 21 sockets connected
disconnect:  pAfXOEz6RocKZdoZAAAb
 21 sockets connected
disconnect:  DIhTyVgG2LYBawaiAAAc
 21 sockets connected
disconnect:  W4XOc1iRymfTE2U0AAAd
 21 sockets connected
disconnect:  WZzegGPcoGDNLRTGAAAe
 21 sockets connected
 22 sockets connected
disconnect:  KVR3-fYH0cz77BmgAAAC
disconnect:  ANQknhnxr4l-OAuIAAAD
disconnect:  KZE5orNx6u9MbOArAAAE
disconnect:  TS6LL3asXrcznfcPAAAF
disconnect:  SVNxS3I7KqecdqKhAAAG
disconnect:  IE2WE5Y0PJzvxgBfAAAH
disconnect:  v69bdJav9PjpThBGAAAI
disconnect:  mJKT1ggfOOTshZKgAAAJ
disconnect:  YlycVjdcWe0emCAcAAAK
disconnect:  MoIDJSzP_L-1RUwuAAAM
disconnect:  wAl0x5qwCkrnDDYQAAAN
disconnect:  eiTlPEk2Hx_X-L-fAAAO
disconnect:  KgkrXxzG_EpXOsPTAAAQ
disconnect:  Lvf3kK-6XXEbu3NWAAAS
disconnect:  -hOoGdYOIvVK04K_AAAT
disconnect:  3EUmaAYpK-U3Ss9tAAAU
disconnect:  HQ6M98FebtKlU3OfAAAW
disconnect:  OwgrbRBYbS4j84nmAAAX
disconnect:  yN8FZAP4RjUNl2MeAAAZ
disconnect:  K9IFTjlgAWzdNfpUAAAf

我的问题是: 这是一个 Bug 还是 socket.io 的正常行为?如何防止简单地按下 F5 导致连接洪水泛滥?

最好的问候 Marc


当你刷新浏览器时,浏览器应该关闭打开的WebSockets,并且你应该收到一个断开连接的消息。如果没有,那可能是浏览器的一个bug。如果浏览器不能立即关闭WebSocket,socket.io会通过心跳检测发现该套接字不再活动(因为心跳失败),最终将清理该套接字。 - jfriend00
1
这在所有现实浏览器中都会发生。 - mcbookwood
1
如果您只按一次F5,则立即出现断开连接。但是,如果您足够快地按下F5,则会“丢失”一些断开连接。我认为计数器没有问题,因为示例服务器仅在新连接打开时进行计数。 - mcbookwood
我的关于计数器的评论是,你会收到一个断开连接的消息,但计数不会减少。这在你的日志中似乎非常明显。如果只是快速刷新导致了问题,我不知道你为什么会担心这个问题。它们会在短时间内自行清理,因此不会意外地长期积累任何东西。这会引起任何实际问题吗?你可以缩短socket.io的不活动超时时间,以便更快地清理它们,但这可能会产生其他妥协,所以如果它实际上没有引起真正的问题,我不会进行更改。 - jfriend00
@mcbookwood请看一下我在这里回答自己问题的答案 - 它对我有用。https://stackoverflow.com/questions/48137228/socket-io-changefeed-multiple-emits-on-refresh-reload/ - Bolli
显示剩余2条评论
3个回答

43

我自己制作了一个测试应用程序,成功地找出了问题所在。

如果你快速地按下 F5 按钮多次,Chrome 浏览器会暂时累积一些额外的 socket.io 连接,但在相对较短的时间内(可能几分钟),它会恢复,已连接套接字的总数将回到1。

经过进一步测试,我发现这不是浏览器的问题。这是 socket.io 如何启动 socket.io 连接的问题。如果你在客户端中更换如下内容:

var socket = io();

用这个:

var socket = io({transports: ['websocket'], upgrade: false});

如果强制socket.io仅使用webSocket而不使用HTTP轮询,则问题会消失。

因此,问题的原因是socket.io的默认行为是从一个socket.io连接的http轮询版本开始。在交换了一些数据后,socket.io将尝试切换到真正的Web套接字。如果真正的Web套接字可用,它将停止使用http轮询连接。

但是,如果在轮询和真正的Web套接字之间的这个转换过程中按下F5,则对于socket.io来说还没有持久连接,以便知道刚刚通信的Web页面现在已经不存在。因此,它所能做的就是在稍后的某个时间找出不再有来自该Web页面的传入通信,因此应清除其socket.io连接(在按下F5时它处于轮询模式)。

但是,如果使用上述客户端代码关闭初始轮询模式,则它只会使用真正的Web套接字(永远不使用模拟轮询模式),而浏览器在按下F5时清除Web套接字非常好,因此服务器要么尚未完成建立其socket.io连接(在这种情况下,还没有临时孤立的连接),要么已经转换为Web套接字(并且浏览器将在F5上干净地关闭它)。

因此,这是socket.io启动时的http轮询模式的设计限制。由于在该模式下没有持续的连接,因此浏览器在使用F5替换该页面时不会立即发出通知,因此服务器无法知道客户端刚刚消失。但是,如果跳过http轮询模式并以真正的Web套接字开始,则不存在这样的时间窗口,在那里有一个socket.io连接,但没有真正的Web套接字,因此当页面消失时,浏览器关闭Web套接字连接时服务器始终会立即收到通知。


1
啊,好的,那很有道理。在编辑客户端和服务器上的套接字连接后,问题就不再出现了。感谢您的解释! - mcbookwood
@jfriend00 - 这对解决页面刷新非常有帮助。我一直在使用 Flask-Socket-IO 作为后端,但这种技术应该始终适用于客户端 Socket.IO。 - kta

2
解决方案:更新前端和后端的socket.io包版本。
例如:后端使用socket.io 3.0.3,前端使用socket-io-client 3.0.3。
现在它们是兼容的,并且传输套接字是websocket而不是轮询!
从...导入io
const socket = io('http//localhost:3000') -> 服务器节点

这完全解决了我的问题,前端和后端有不同的版本。 - hummmingbear

0

我遇到了同样的错误,我发现使用相同用户时出现了多个连接。后来发现问题是在后端使用了不同版本,并在前端添加了以下代码:

socket.on("connect_error", (err) => { console.log(`连接错误: ${err.message}`); });

这样,我可以知道错误的具体信息,希望对其他人也有所帮助。


此回答与问题无关。 - Alann Maulana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接