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 状态。

需要等待 2MSLMaximum 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 的函数签名如下:

int shutdown(int sock, int howto)

其中 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

参考

猿人谷 - 关于三次握手和四次挥手

小白debug - 活久见!TCP两次挥手,你见过吗?那四次握手呢?

Last updated