TIME_WAIT、CLOSE_WAIT

12/12/2015来源:Java教程人气:2319

 内容提要

  • TCP连接释放的过程
  • TIME_WAIT
    • 大量TiME_WAIT的后果
    • 解决方案
  • CLOSE_WAIT
    • 大量CLOSE_WAIT的后果
    • 解决方案

 

 

TCP 连接释放的过程

 

 

图中,A是主动关闭的一方,B是被动关闭的一方。

 从图中可以看出,如果处于TIME_WAIT状态,说明是主动关闭的。如果处于CLOSE_WAIT,说明是被动关闭的。

 

TIME_WAIT

         从图上看出,TIME_WAIT状态下,会有2个MSL(最大报文存活时间)的等待时间,之后才会处于CLOSED状态,并释放资源。

 

 

         为了保证A发送的最后一个ACk报文段能够到达B,其实就是为了让B真正的关闭,这样才能够保证连接的双方都关闭。毕竟是全双工的通信,总不能出现A到B的连接是关闭的,B到A的连接是打开的,这样只能算是半关闭状态。

MSL 的默认值是120s,也就是2分钟,所以2MSL就是4分钟。也就是说主动关闭的一方,会等待4分钟才会释放相关资源。

 

出现TIME_WAIT会带来的后果

1) 如果客户端出现了大量的TIME_WAIT,那么在这4分钟之内,想要再使用该Socket绑定的端口,肯定会报BindException了。

2) 如果服务端出现了大量的TIME_WAIT,则服务的性能会下降,因为底层有大量的Sturct  Socket没有释放,占内存,在用户量很大时,会很卡。

3) 服务端重启时,ServerSocket绑定的端口可能会处于TIME_WAIT状态,也是会出现BindException异常的。

 

TIME_WAIT解决方案

         从程序设计的角度讲:

                 服务端最好不要(或者少)出现TIME_WAIT状态,毕竟这个问题是不可避免的。也就是要把TIME_WAIT分散到客户端,即让客户端主动关闭连接。

         从运维的角度来考虑讲:

                 就需要对操作系统的配置做调整了:

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间,单位s

  

CLOSE_WAIT

         根据上面的TCP连接释放过程中可以看出,如果一方出现了CLOSE_WAIT状态,说明在连接被动关闭后,没有发送FIN报文段到对方。

        

    

 

         默认情况下,操作系统设定的Keepalive时间是2小时。

 

大量CLOSE_WAIT会带来的后果

         如果发生在客户端,说明连接是被服务端关闭的。客户端的这些端口在这CLOSE_WAIT状态内,是不能够使用了。如果要使用,应会报BindExcepion了。

         如果发生在服务端,说明是客户端关闭的,服务端同样会变的更慢了。出现这种情况就要在服务端找原因了。

 解决方案

         从程序设计角度讲

             1) 在另一端出现关闭了连接后,出现CLOSE_WAIT的一端为什么没有相应的执行关闭连接操作。

             2) 如果这种现象发生的客户端,还要去查为什么服务端要关闭这些连接,服务端该不该关闭。

    

    

操作系统中 TCPIP协议栈的底层实现:
TCP 建立连接以后,双方就是对等的。不论是哪一方,只要正常 close(socket_handle),那么 TCP 底层软件都会向对端发送一个 FIN 包。 FIN 包到达对方机器之后,对方机器的 TCP 软件会向应用层程序传递一个 EOF 字符,同时自动进入断开连接流程(要来回协商几次,但这些都是自动的、不可控的)。什么是 EOF 字符?它其实什么也不是,只是一个标记,上层应用程序如果这时读 socket 句柄的话,就会读到 EOF,也就是说,此时 socket 句柄看起来里面有数据,但是读不出来,因此 select 返回可读(非阻塞模式下)read 不会阻塞(阻塞模式下)但是 read 的返回值却是 0。 如果此时不是读操作而是写操作,并且此时 socket 已经断开连接,那么 write 函数会返回 -1 且置 errno 为 EPIPE(如果忽略了 SIGPIPE 信号的话)或者引发 SIGPIPE 信号(如果没忽略的话)

    

          如果从代码上去解决这种问题,就应该是执行Socket(或者SocketChannel)的close()方法。执行这个方法时,才会去发送FIN报文段。

  

READ:
NIO:
if(socketChannel.read()==-1){ socketChannel.close(); } BIO: InputStream input=socket.getInputStream(); ... if(inpout.read()==-1){ input.close(); socket.close(); }

WRITE:
写的过程出现异常,就可以关闭了

 

 

        从运维的角度讲

tcp_keepalive_time :INTEGER
默认值是7200(2小时)
当keepalive打开的情况下,TCP发送keepalive消息的频率。(由于目前网络攻击等因素,造成了利用这个进行的攻击很频繁,曾经也有cu的朋友提到过,说如果2边建立了连接,然后不发送任何数据或者rst/fin消息,那么持续的时间是不是就是2小时,空连接攻击? tcp_keepalive_time就是预防此情形的.我个人在做nat服务的时候的修改值为1800秒)

tcp_keepalive_PRobes:INTEGER
默认值是9
TCP发送keepalive探测以确定该连接已经断开的次数。(注意:保持连接仅在SO_KEEPALIVE套接字选项被打开是才发送.次数默认不需要修改,当然根据情形也可以适当地缩短此值.设置为5比较合适)

tcp_keepalive_intvl:INTEGER
默认值为75
探测消息发送的频率,乘以tcp_keepalive_probes就得到对于从开始探测以来没有响应的连接杀除的时间。默认值为75秒,也就是没有活动的连接将在大约11分钟以后将被丢弃。(对于普通应用来说,这个值有一些偏大,可以根据需要改小.特别是web类服务器需要改小该值,15是个比较合适的值)