1.断网场景描述
我之前写过一个nio demo,当服务端通过blade工具进行100%丢包测试时,发现原先的tcp连接在断网后,客户端通过netstat工具查看到的tcp连接还是处于ESTABLISHED状态,没有断开。
等待一段时间后,tcp连接才断开。
1.1.客户端报错
客户端在tcp端开后,有如下报错信息:
Exception in thread "main" java.io.IOException: Connection reset by peer
at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:379)
at SocketChannelClientTest.main(SocketChannelClientTest.java:23)
1.2.服务端报错
服务端报错信息如下所示:
Exception in thread "main" java.io.IOException: No route to host
at java.base/sun.nio.ch.SocketDispatcher.read0(Native Method)
at java.base/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:47)
at java.base/sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:330)
at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:296)
at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:259)
at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:417)
at SelectorServer.main(SelectorServer.java:47)
下面,我们从这个场景入手,分析断网场景中服务端和客户端的表现。
2.影响tcp连接的因素
tcp连接有以下机制保证。
2.1.tcp keepalive机制
tcp自身有个keepalive机制,用于保证连接双方能够保持一定的心跳,保证网络链路的通畅。
keepalive机制的控制由以下三个操作系统级别参数决定。
2.1.1.tcp_keepalive_time
表示发送一次keepalive的报文的间隔时间,单位是秒。
该参数对应Socket的TCP_KEEPIDLE选项,通过setsocketopt进行设置。
可通过以下方式查看该参数的配置值:
[root@ ~]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@ ~]#
上述案例表示每隔7200秒发送一次keepalive的报文。
2.1.2.tcp_keepalive_intvl
在发送keepalive报文没有响应的情况下,需要重试,tcp_keepalive_intvl表示重试的时间间隔,单位为秒。
该参数对应Socket的TCP_KEEPINTL选项,通过setsocketopt进行设置。
可通过以下方式查看该参数的配置值
[root@ ~]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@ ~]#
上述案例表示如果没有回应,则75秒后重试。
2.1.3.tcp_keepalive_probes
在发送keepalive报文没有响应的情况下,需要重试,tcp_keepalive_probes表示最多重试次数。
该参数对应Socket的TCP_KEEPCNT选项,通过setsocketopt进行设置。
可通过以下方式查看该参数的配置值。
[root@ ~]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@ ~]#
上述案例表示如果没有回应,最多重试9次即认为连接关闭。
2.1.4.tcp keepalive机制存在的问题
正常情况下,连接的一方主动调用close关闭连接时,另一方会收到通知。但是如果tcp连接的另一端突然掉线,或者重启断电,这个时候我们并不知道网络已经关闭。此时如果发送数据失败,tcp会自动进行重传。重传包的优先级高于keepalive,那就意味着,我们的keepalive总是不能发送出去,而此时,我们也不知道该连接已经出错而中断。在较长时间的重传失败之后,我们才会知道。
2.2.tcp重传机制
如果通信双方出现网络错误,比如丢包,则会触发tcp连接的重传机制,重传未得到响应的报文。
如果在重传报文的过程中,网络恢复,由于丢包100%并不会改变TCP连接状态,并且还是处于ESTABLISHED状态,如果一方接收到新的报文,则会回ACK响应报文。
tcp重传机制与以下三个系统级参数相关。
2.2.1.tcp_retries2
表示重传最多次数。
可通过以下方式查看和设置tcp_retries2
[root@ ~]# cat /proc/sys/net/ipv4/tcp_retries2
15
[root@ ~]#
net.ipv4.tcp_retries2
2.2.2.tcp_rto_max
表示相邻两次重传之间最大时间间隔,单位秒。
可通过以下方式查看和设置tcp_rto_max
[root@ ~]# cat /proc/sys/net/ipv4/tcp_rto_max
120
[root@ ~]#
net.ipv4.tcp_rto_max
2.2.3.tcp_rto_min
表示相邻两次重传之间最小时间间隔,单位毫秒。
可通过以下方式查看和设置tcp_rto_min
[root@ ~]# cat /proc/sys/net/ipv4/tcp_rto_min
200
[root@ ~]#
net.ipv4.tcp_rto_min
2.2.4.重传机制中等待时间
每次重传于开始重传的时间起点不是线性关系的。下面以tcp_retries2=15,tcp_rto_min=200,tcp_rto_max=120为例,解析每次重传的等待时间,如下表所示:
当tcp_retries2为15时,如果重传15次(或者说15分钟24.8秒)后还无法成功,就断开连接。