> For the complete documentation index, see [llms.txt](https://wtifs.gitbook.io/diva-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wtifs.gitbook.io/diva-notes/network/tcp-si-ci-hui-shou.md).

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

![img](https://4237436490-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhDxW69eCVrvTGotZqcT%2Fuploads%2Fgit-blob-0d95fac50fa820fea0c499cd563e61691f9431ae%2Fv2-c7d4b5aca66560365593f57385ce9fa9_720w.jpg?alt=media)

其中第一次挥手和第三次挥手，是我们在应用程序中主动触发的（ `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` 的函数签名如下：

```c
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`。

#### 参考

> [猿人谷 - 关于三次握手和四次挥手](https://blog.csdn.net/hyg0811/article/details/102366854)
>
> [小白debug - 活久见！TCP两次挥手，你见过吗？那四次握手呢？](https://mp.weixin.qq.com/s/Z0EqSihRaRbMscrZJl-zxQ)
