TCP四次挥手
建立一个连接需要三次握手,而终止一个连接要经过四次挥手。这由 TCP
的半关闭(half-close)造成的。所谓的半关闭,其实就是 TCP
提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
TCP
的四次挥手,客户端或服务器均可主动发起。
这里以客户端主动发起为例,刚开始双方都处于 ESTABLISHED
状态。四次挥手的过程如下:
第一次挥手
客户端发送一个 FIN
报文,报文中会指定一个序列号 SEQ
,停止发送数据。进入 FIN_WAIT1
状态。
一般情况下,主动方执行 close()
或 shutdown()
方法,会开启这个流程。
第二次挥手
服务端收到 FIN
之后,会回复 ACK
报文,且把客户端的 序列号值 SEQ +1
作为 ACK
的值,表明已经收到客户端的报文了,进入 CLOSE_WAIT
状态。
客户端收到服务端的确认后,进入 FIN_WAIT2
状态,等待服务端发送完剩余的数据。此时的 TCP
处于半关闭状态。
第三次挥手
服务端没有数据发给客户端后,也向客户端发送 FIN
报文,进入 LAST_ACK
状态,等待客户端最后的确认。
四次挥手比三次握手多一步,就是把三次握手里的第二次拆成了四次挥手里的第二、三次。因为服务端收到客户端的 FIN
后,可能有数据还没发完,所以只能先回复一个 ACK
报文。等到数据都发完了,再调用 close()
,主动发送一次 FIN
报文。
第四次挥手
客户端收到 FIN
之后,一样发送一个 ACK
报文作为应答,把服务端的 SEQ +1
作为自己 ACK
报文的序列号值,进入 TIME_WAIT
状态。
需要等待 2MSL
(Maximum Segment Lifetime
,最大报文生存时间)以确保服务端收到 ACK
报文之后才会进入 CLOSED
状态,服务端收到 ACK
报文之后,就关闭连接了,进入 CLOSED
状态。
如果服务端没有收到这次 ACK
,会重发 FIN
。客户端从发出 ACK
,到收到服务端重发的 FIN
,是两个网络来回,因此等待时间是 2MSL
。
如果没有这次挥手,那客户端收 FIN
的过程可能失败,就会一直处于 FIN_WAIT2
状态。
其中第一次挥手和第三次挥手,是我们在应用程序中主动触发的( go
里由 net/http
库完成,调用 shutdown / close
方法触发的)。
第二和第四次挥手,是内核协议栈自动帮我们完成的,应用程序不需要太关心。
另外不管是主动还是被动,每方发出了一个 FIN
和一个 ACK
。也收到了一个 FIN
和一个 ACK
。
问题
FIN 一定要程序执行 close() 或 shutdown() 才能发出吗?
不一定。应用程序被 kill
也会发出 FIN
。
FIN
是指 "我不再发送数据",因此 shutdown()
关闭读不会给对方发 FIN
,关闭写才会发。
机器上FIN-WAIT2状态特别多,是为什么
处于这个状态的程序,一直在等第三次挥手的 FIN
。一般是因为对端一直不执行 close()
方法发出第三次挥手。
一般来说,对端机器上会有大量的 CLOSE_WAIT
。需要检查对端机器,为什么迟迟不调用 close()
关闭连接。
close 和 shutdown 的区别
shutdown
的函数签名如下:
其中 howto
为断开方式。有以下取值:
SHUT_RD
:关闭读。这时应用层不应该再尝试接收数据,内核协议栈中就算接收缓冲区收到数据也会被丢弃。发送数据的对端则会收到connection reset by peer
的报错。SHUT_WR
:关闭写。如果发送缓冲区中还有数据没发,会将将数据传递到目标主机。SHUT_RDWR
:关闭读和写。这种等同于close()
。
如果服务器一直不发第三次挥手,会怎么样
客户端会根据自身第一次挥手的时候用的是 close()
还是 shutdown(fd, SHUT_WR)
,有不同的行为表现。
如果是
shutdown(fd, SHUT_WR)
,说明客户端其实只关闭了写,但还可以读,此时会一直处于FIN-WAIT-2
, 死等被动方的第三次挥手。如果是
close()
, 说明客户端读写都关闭了,这时候会处于FIN-WAIT-2
状态2MSL
。超过这段时间之后,状态直接变成CLOSED
。
参考
Last updated