今天项目中发现之前做的websocket总有隔一段时间后断开连接的情况, 原因是nginx服务器由于ws连接长时间无数据判定连接断开,如果更改ngnix参数可以改善, 但是为了优化这个问题,并且提高系统可靠性,采用了心跳机制
为什么需要心跳
首先,长连接是建立在TCP协议上的
TCP连接状态
TCP建立的连接是通过三次握手建立的,是一种逻辑上的连接, 如果链路已经断开,但是TCP层仍然认为连接还是存在的(ESTABLISHED), 所以应用层也同样感知不到链路不通的问题
TCP KeepAlive机制
TCP自带KeepAlive机制
但是这个机制不是TCP协议规范中的,由操作系统实现, 如果操作系统不进行定期清除失活的连接,会导致网络性能下降, 开启KeepAlive机制后, 在一定时间(tcp_keepalive_tim参数)内,链路上如果没有数据传输,TCP层将会发送相应的KeepAlive探针以确定连接可用,探测失败后重试10(tcp_keepalive_probes)次,每次间隔时间75s(tcp_keepalive_intvl参数), 所有探测失败后,认为当前连接已经不可用.参数都是系统级别
调整参数可以改善连接问题,但是参数的调整切换机器后也要调整,十分不便利
如果有数据发送,但是物理链路不通,操作系统的链路状态还是ESTABLISHED,会走TCP重传机制,TCP超时重传的指数退避算法也相当耗时间.
因此,长连接的保活肯定依赖应用层的心跳来保证
应用层心跳好处
能够及时发现链路故障问题,尽早建立新的连接,故障转移,可以重连,也可以请求其他服务器
尤其是服务器CPU重度使用,线程池爆满的情况下
Node.js示例
使用方法参照官网说明: https://www.npmjs.com/package/ws
How to detect and close broken connections?
Sometimes the link between the server and the client can be interrupted in a way that keeps both the server and the client unaware of the broken state of the connection (e.g. when pulling the cord).
In these cases ping messages can be used as a means to verify that the remote endpoint is still responsive.
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 const WebSocket = require('ws');
function noop() {}
function heartbeat() {
this.isAlive = true;
}
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
const interval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on('close', function close() {
clearInterval(interval);
});