Node.js服务器TCP死连接问题诊断

最近一段时间,由于开发工作开始跟嵌入式相关,开始遇到一个问题:TCP死连接。

TCP死连接症状是这样的:通信双方从一方系统上看已经断开(不存在),但是另一方系统上看却是连接中(ESTABLISHED状态)。

TCP死连接一般在一方(或中间线路上的设备)断电、死机后出现,此时由于另一方收不到断开连接的IP报文,会认为连接仍然存在,日积月累会耗光文件描述符空间从而导致性能下降,最终拒绝服务。

对付这种问题,一般需要双方都进行连接心跳检测。比如:连接空闲一段时间后一方发一个心跳请求,另一端回个心跳响应,心跳请求发送方一段时间后还收不到响应则认为连接已断开,心跳请求接收方一段时间内没有收到心跳请求也认为连接已断开。

需要注意到的是node.js的tls服务器端握手超时处理不当可能会导致TCP死连接出现,有问题的代码示例如下:

var options = {
    key: "...",
    cert: "...",
    handshakeTimeout: 10*1000,
    plain: true,
    ssl: true
};

var tlsServer = tls.createServer(options, app).listen(5433, 8192, function(){
    logger.log('tls server listening on port 5433');
});

tlsServer.on('clientError', function (exception, socket) {
    logger.warn('tls server client(' + socket.remoteAddress + ':' + socket.remotePort +') error(' + exception + ')');
});

上面的代码通过指定 handshakeTimeout 使用指定SSL握手超时时间,但是并未关闭底层的TCP连接,从而导致TCP连接泄露,在 clientError 事件处理函数中添加以下释放语句即可:

socket.destroy();

除了常见的断电、死机引起TCP死连接外,这里还有一个论坛帖子论坛其它原因:《Too many established connections left open》。

另外还有 linux 内核的 tcp keepalive机制作为心跳解决方案:《linux下使用TCP存活(keepalive)定时器》。

谨记:除了主动通过连接发送数据外,其它情况下操作系统可能不会告诉你连接已经关闭了。

要彻底解决这个问题,除了要避免泄露(或忘记关闭)TCP连接外,要有心跳机制,还需要从代码层面进行防御性编程,如:对于读写操作设置超时时间,一旦超时主动关闭连接。