【精品】TCP四次挥手和TIME_WAIT

2015/11/07 - 计算机网络

FIN_WAIT_1 : FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是: FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

FIN_WAIT_2 :实际上FIN_WAIT_2状态下的SOCKET,表示半连接 ,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。

TIME_WAIT : 表示收到了对方的FIN报文,并发送出了ACK报文 ,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态同时关闭。(主动方)

CLOSING(比较少见) : 这种状态比较特殊,实际情况中应该是很少见。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

CLOSE_WAIT : 表示在等待关闭。当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。

LAST_ACK : 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。

CLOSED : 表示连接中断。

为什么需要四次挥手?

那可能有人会有疑问,在tcp连接握手时为何ACK是和SYN一起发送,这里ACK却没有和FIN一起发送呢。原因是因为tcp是全双工模式,接收到FIN时意味将没有数据再发来,但是还是可以继续发送数据(解决全双工问题)。

TIME_WAIT

TIME_WAIT状态会持续2MSL的时间才会转换到CLOSE状态,一般是1-4分钟(现在对于linux是30秒)。MSL(最大分段生存期):指明TCP报文在Internet上最长生存时间。

只有主动关闭的一方才会进入TIME_WAIT状态。那么端口不够用就是文件描述符不够用了,因为文件描述符只有在从TIME_WAIT状态转换到CLOSE状态后才会真正被系统收回。

问题

如果执行主动关闭的一方HOST1不进入到TIME_WAIT状态就关闭连接那会发生什么呢?

当重传的FIN消息到达时,因为TCP已经不再有连接的信息了,所以就用RST(重新启动)消息应答,导致HOST2进入错误的状态而不是有序终止状态(如果主动关闭的一方又开启了一个新的链接,则重发的FIN会将新连接给关闭掉),如果发送最后ACK消息的一方HOST1处于TIME_WAIT状态并仍然记录着连接的信息,它就可以正确的响应HOST2的FIN消息了。(最后一个ACK可能丢失并导致HOST2重新发送FIN消息,导致老连接的包会干扰新连接

TIME_WAIT占用的资源

  1. TW会占用内存,但是占用内存很小(1W的TW连接占用1M左右)
  2. 对CPU也是有影响的,比如TW的端口太多,导致选择可用端口时,需要很多次选择才能成功;但这个影响也是很小的。
  3. TW连接会占用大量端口。
  4. 被占用的是一个五元组:(协议,本地IP,本地端口,远程IP,远程端口)。对于Web 服务器,协议是TCP,本地IP通常也只有一个,本地端口默认的80或者443。只剩下远程 IP和远程端口可以变了。如果远程IP相同的话,就只有远程端口可以变了。这个只有几万个(net.ipv4.ip_local_port_range),所以当同一客户端向服务器建立了大量连接之后,会耗尽可用的五元组导致问题。

https://www.zhihu.com/question/29354418?sort=created http://www.cnblogs.com/lulu/p/4149312.html

TIME_WAIT优化

tcp_timestamps

只有该选项开启了,tcp_tw_reuse和tcp_tw_recycle才能起作用。另外tcp_timestamps可以解决PAWS和计算RTT的问题。

tcp_tw_reuse

需要开启tcp_timestamps时才有效。针对于一个连接,如果开启了该开关,即便该连接处于TW状态,收到SYN包之后,也能建立一条新的连接。该连接跟之前的TW连接5元组相同。但是需要满足下面条件的其中一个:

  1. 初始化序列号比老的TW序列号大。
  2. 新来的连接的时间戳比老的TW的时间戳大。

tcp_tw_recycle

快速回收TW,应该是RTO时间内回收。需要开启tcp_timestamps时才有效。 即使快速回收之后,也保留了一些信息,保留的信息有:对端IP、最后的对端过来的时间戳等。对于新来的连接,同时满足下面3个条件时,连接会被拒绝;否则连接不会被拒绝:

  1. 来自该IP的TCP连接请求带有时间戳信息;
  2. 在MSL时间内,收到过该IP过来的数据;
  3. 新连接的时间戳小于保存的TW的时间戳;

tcp_max_tw_buckets

这个是控制并发的TIME_WAIT的数量,默认值是50几W,如果超限,那么,系统会把多的给destory掉,然后在日志里打一个警告。

SO_LINGER

struct linger {
    int l_onoff; /* 0 = off, nozero = on */
    int l_linger; /* linger time */
};
  1. 设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据。
  2. 设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;(说明如果没有数据被丢弃,也是正常的四次挥手;不是rst)
  3. 设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。

TCP关闭连接的方式

正常关闭

调用close()关闭socket、没close但进程正常结束(当然这是不应该的做法)、进程core掉、在shell 命令行中kill掉进程,都可抽象成“正常”关闭。因为即使core掉,内核也会马上帮应用程序回收(close)socket文件描述符。

不正常关闭

客户端崩溃了,此时肯定发不出FIN包了(当然啦,内核都没机会帮应用程序回收资源了)。这种情况,服务器端有如下两种情况。

1、服务器send数据,因为客户端已经崩溃,服务器收不到ACK自然会不停的重传。Berkeley的重传机制,重传8次,相对第一次传的15分钟后仍没收到ACK,则返回ETIMEDOUT或EHOSTUNREAC错误。如果服务器不理会这个错误,再次调用send,则立马返回Broken Pipe错误。

2、服务器不发任何数据了,那只有靠应用层心跳检测机制或Keepalive,来发觉TCP断连了。


如果文章对您有帮助,欢迎扫描下方二维码赞助(一分也是爱噢),谢谢

Search

    一分也是爱噢 一分也是爱

    目录