# 【NO.487】TCP三次握手、四次挥手以及TIME_WAIT详解

### 1.前提概述

TCP网络编程中常用的api函数有：

socket、bind、listen、accept、recv、send、close、connect

其中socket函数返回一个文件描述符fd，这个fd并不单纯，而是对应着内核创建的TCB（transport control block），可以理解为一个下标索引，而不同TCB则是根据不同的五元组（源ip、源port、目的ip、目的port、协议类型）来进行区分。

bind函数则是将socket函数创建的fd和本机ip联系起来。

listen的功能是通知协议进程准备接收socketfd 上的连接请求，套接字也将从CLOSED转换到LISTEN状态，它同时也指定socket上可以排队等待的连接数的门限值。超过门限值时，socket将拒绝进入的连接请求排队等待。当这种情况出现时，TCP将忽略进入的连接请求，进程可以通过调用accept来得到队列中的连接。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173232_27144.jpg)

### 2.三次握手过程

在服务端调用listen函数进行监听时，客户端就可以准备通信了，而在通信之前自然离不开一些准备工作了，也就是常说的三次握手。

当客户端调用connect函数时，三次握手也随之发生，如图：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173232_91736.jpg)

1、客户端首先会发送一个SYN分节（SYN位置位），它将告诉服务器客户端将在连接中发送的数据初始序列号，通常该数据包只有包头。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173233_68098.jpg)

当服务端收到客户端的第一个SYN分节数据包时，服务端将为本次连接建立对应的TCB并存入一个所谓的半连接队列当中，此时半连接队列中的套接字都将处于SYN_RCVD状态。

2、服务器必须确认客户端发送的SYN，置位SYN和ACK位。如图：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173234_43101.jpg)

客户端在收到服务器的确认之后，connect将返回0，即建立连接成功。

3、客户端最后再对服务器发送的SYN进行确认，如图：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173234_77736.jpg)

当服务器收到来自客户端的确认时，处于半连接队列中的套接字将被移到已完成连接队列的队尾，当进程调用accept时，将从已完成连接队列中的队头项返回给进程。此外，listen函数中的第二个参数backlog在一些系统中规定为两个队列之和的最大值，而在有些系统中则是已完成连接队列和的最大值。当队列已满时，服务端将会忽略客户端发送的SYN分节。

此时已连接队列中的套接字都将处于ESTABLISHED状态，即双方连接建立。

### 3.三次握手过程中的问题

**为什么需要三次握手？**

三次握手其实是为了建立双方之间的通信，即双方都要确认通信状态，而不是单向的通信。如果变成两次握手，那么服务器不能确认客户端的接收状态，此外，服务器发送的ACK数据包若丢失，客户端将拒绝后续的数据，则服务器一直在发送，而客户端一直再拒绝。

### 4.四次挥手

挥手过程可以理解为一对情侣的分手场景，如图：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173235_76269.jpg)

结合三次握手过程，我们能很快清楚四次挥手的过程，继续情侣分手的场景，当一方提出分手时，另一方会做出回应，当然后续会有一些争执或者收拾彼此的东西，然后另一方觉得确实没办法继续在一起了，于是也说出了分手，此时当初提出分手的一方会很干脆的进行确认，此后两方再无瓜葛。

### 5.四次挥手过程中的问题

**1、两端可能同时close吗，此时什么场景？**

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/15/20221215173235_99373.jpg)

**2、主动方出现大量的TIME_WAIT？TIME_WAIT作用？**

这种情况一般发生在高并发短连接的场景，一般可以通过设置reuseaddr的方法来解决。

TIME_WAIT状态作用：

主动关闭放发送最后一个ack包后，可能出现丢失（这时对方还会重发FIN，收到两个FIN的时间间隔一定小于2MSL），使对方没有收到最后一个ACK包时有时间可以重发ACK包；

防止前一个连接中老的分组在新连接中再现，TIME_WAIT存在时间为2MSL，而某个连接上的分组最多存活1MSL就会被丢弃。

**3、出现大量的CLOSE_WAIT状态？**

被动方处于在收到FIN分节时，处于CLOSE_WAIT，接着就需要进行程序收尾工作，一旦耗时操作比较多，对应的CLOSE_WAIT时间就越长。此时需要调整代码逻辑，即时的调用close函数。

**4、出现大量的FIN_WAIT1/2状态？**

此时对端没有即时发送ACK，而主动方也没有其他办法过渡到TIME_WAIT状态，只能选择kill掉进程。

**5、socket描述符和对应的TCB控制块回收的时间点？**

调用close函数后fd被立即回收；

而TCB的话，被动方在接收到ack后被回收，主动方则在TIME_WAIT时间到之后再进行回收。

原文链接：https://zhuanlan.zhihu.com/p/499127830

作者：[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10)