深入浅出TCP四次挥手 (多图详解)

4,068 阅读11分钟

在这里插入图片描述

「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战

前言

TCP三次握手和四次挥手是面试题的热门考点,它们分别对应TCP的连接和释放过程。在深入浅出TCP三次握手的文章中,我们详细讲解了三次握手的过程,今天我们继续深入学习TCP四次挥手,以及相关面试问题解答。

1、TCP的连接释放

  • TCP 连接释放过程比较复杂。
  • 数据传输结束后,通信的双方都可释放连接。
  • TCP 连接释放过程是 四报文挥手

2、TCP通过“四报文挥手”来释放连接

  • TCP 连接的建立 采用客户服务器方式
  • 主动发起连接建立的应用进程叫做 TCP客户 (client)。
  • 被动等待连接建立的应用进程叫做 TCP服务器 (server)。
  • 任何一方都可以在数据传送结束后发出连接释放的通知

3、四次挥手图文详解

数据传输结束后,TCP通信双方者都可以释放连接, 现在TCP客户进程和TCP服务器进程都处于连接已建立(ESTABLISHED)状态。

假设使用TCP客户进程的应用进程通知其主动关闭TCP连接。TCP客户进程会发送TCP连接释放报文段,并进入终止等待1(FIN-WAIT-1)状态。

TCP连接释放报文段首部中:

  • 终止位FIN和确认为ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段进行确认。
  • 序号seq字段的值设置为u,它等于TCP客户进程之前已传送过的数据的最后一个字节的序号加1。
  • 确认号ack字段的值设置为v,它等于TCP客户进程之前已收到的、数据的最后一个字节的序号加1。

请注意: TCP规定终止位FIN等于1的报文段即使不携带数据,也要消耗掉一个序号。

TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段并进入关闭等待(CLOSE-WAIT)状态。

TCP确认释放报文段首部中:

  • 确认位ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
  • 序号seq字段的值设置为v,它等于TCP服务器进程之前已传送过的数据的最后一个字节的序号加1,这也与之前收到的TCP连接释放报文段中的确认号匹配。
  • 确认号ack字段的值设置为u+1,这是对TCP连接释放报文段的确认。

TCP服务器进程应该通知高层应用进程,TCP客户进程要断开与自己的TCP连接。此时,从TCP客户进程到TCP服务器进程这个方向的连接就释放了。这时的TCP连接属于半关闭状态,也就是TCP客户进程已经没有数据要发送了。但如果TCP服务器进程还有数据要发送,TCP客户进程仍要接收,也就是说从TCP服务器进程到TCP客户进程这个方向的连接并未关闭,这个状态可能要持续一段时间。

TCP客户进程收到TCP确认报文段后就进入终止等待2(FIN-WAIT-2)状态,等待TCP服务器进程发出的TCP连接释放报文段。 若使用TCP服务器进程的应用进程已经没有数据要发送了,应用进程就通知其TCP服务器进程释放连接。由于TCP连接释放是由TCP客户进程主动发起的,因此TCP服务器进程对TCP连接的释放称为被动关闭连接

TCP服务器进程发送TCP连接释放报文段并进入最后确认(LAST-ACK)状态。

TCP连接释放报文段首部中:

  • 终止位FIN和确认位ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段进行确认。
  • 假定序号seq字段的值为w,这是因为在半关闭状态下,TCP服务器进程可能又发送了一些数据。
  • 确认号ack字段的值为u+1,这是对之前收到的TCP连接释放报文段的重复确认。

TCP客户进程收到TCP连接释放报文段后,必须针对该报文段发送普通的TCP确认报文段,之后进入时间等待(TIME-WAIT)状态。

TCP确认报文段首部中:

  • 确认为ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
  • 序号seq字段的值设置为u+1,这是因为TCP客户进程之前发送的TCP连接释放报文段虽然不携带数据,但要消耗掉一个序号。
  • 确认号ack字段的值设置为w+1,这是对所收到的TCP连接释放报文段的确认。

TCP服务器进程收到该报文段后就进入关闭状态,而TCP客户进程还要进过2MSL后才能进入关闭状态。 ( MSL(Maximum Segment Lifetime)意思是最长报文段寿命,RFC793建议为2分钟。)

4、四次挥手文字总结

四次挥手即 TCP 连接的释放,这里假设客户端主动释放连接。在挥手之前主动释放连接的客户端结束ESTABLISHED 阶段,随后开始四次挥手:

① 首先客户端向服务器发送一段 TCP 报文表明其想要释放 TCP 连接,其中:

  • 标记位为 FIN,表示请求释放连接;

  • 序号为 Seq = u;

  • 随后客户端进入 FIN-WAIT-1 阶段,即半关闭阶段,并且停止向服务端发送通信数据。

② 服务器接收到客户端请求断开连接的 FIN 报文后,结束 ESTABLISHED 阶段,进入 CLOSE-WAIT 阶段并返回一段 TCP 报文,其中:

  • 标记位为 ACK,表示接收到客户端释放连接的请求;

  • 序号为 Seq = v;

  • 确认号为 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号值加 1 作为本段报文确认号Ack 的值;

  • 随后服务器开始准备释放服务器端到客户端方向上的连接。

客户端收到服务器发送过来的 TCP 报文后,确认服务器已经收到了客户端连接释放的请求,随后客户端结束 FIN-WAIT-1 阶段,进入 FIN-WAIT-2 阶段。

③ 服务器端在发出 ACK 确认报文后,服务器端会将遗留的待传数据传送给客户端,待传输完成后即经 过 CLOSE-WAIT 阶段,便做好了释放服务器端到客户端的连接准备,再次向客户端发出一段 TCP 报文, 其中:

  • 标记位为 FIN 和 ACK,表示已经准备好释放连接了;

  • 序号为 Seq = w;

  • 确认号 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号 Seq 的值加 1 作为本段报文确认号 Ack 的值。

随后服务器端结束 CLOSE-WAIT 阶段,进入 LAST-ACK 阶段。并且停止向客户端发送数据。

④ 客户端收到从服务器发来的 TCP 报文,确认了服务器已经做好释放连接的准备,于是结束 FIN-WAIT-2 阶段,进入 TIME-WAIT 阶段,并向服务器发送一段报文,其中:

  • 标记位为 ACK,表示接收到服务器准备好释放连接的信号;

  • 序号为 Seq= u + 1,表示是在已收到服务器报文的基础上,将其确认号 Ack 值作为本段序号的值;

  • 确认号为 Ack= w + 1,表示是在收到了服务器报文的基础上,将其序号 Seq 的值作为本段报文确认号的值。

随后客户端开始在 TIME-WAIT 阶段等待 2 MSL。服务器端收到从客户端发出的 TCP 报文之后结束LAST-ACK 阶段,进入 CLOSED 阶段。由此正式确认关闭服务器端到客户端方向上的连接。客户端等待完 2 MSL 之后,结束 TIME-WAIT 阶段,进入 CLOSED 阶段,由此完成「四次挥手」。

5、相关面试问题

1、TCP客户进程在发送完最后一个确认报文后,为什么不直接进入关闭状态?而是要进入时间等待状态,2MSL后才进入关闭状态,这是否有必要呢?

来看这种状况,TCP服务器进程发送TCP连接释放报文段后进入最后确认状态。TCP客户进程收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。然后,该TCP确认报文段丢失了,这必然会造成TCP服务器进程对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态。

重传的TCP连接释放报文段到达TCP客户进程。由于TCP客户进程属于关闭状态,因此不理睬该报文段。这必然会造成TCP服务器进程反复重传TCP连接释放报文段, 并一直处于最后确认状态而无法进入关闭状态。

因此时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态。

另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段。

2、为什么要四次挥手?

释放 TCP 连接时之所以需要四次挥手,是因为 FIN 释放连接报文和 ACK 确认接收报文是分别在两次握手中传输的。 当主动方在数据传送结束后发出连接释放的通知,由于被动方可能还有必要的数据要处理,所以会先返回 ACK 确认收到报文。当被动方也没有数据再发送的时候,则发出连接释放通知,对方确认后才完全关闭TCP连接。

3、CLOSE-WAIT 和TIME-WAIT 的状态和意义

在服务器收到客户端关闭连接的请求并告诉客户端自己已经成功收到了该请求之后,服务器进入了CLOSE-WAIT 状态,然而此时有可能服务端还有一些数据没有传输完成,因此不能立即关闭连接,而CLOSE-WAIT 状态就是为了保证服务器在关闭连接之前将待发送的数据发送完成。

TIME-WAIT 发生在第四次挥手,当客户端向服务端发送 ACK 确认报文后进入该状态,若取消该状态,即客户端在收到服务端的 FIN 报文后立即关闭连接,此时服务端相应的端口并没有关闭,若客户端在相同的端口立即建立新的连接,则有可能接收到上一次连接中残留的数据包,可能会导致不可预料的异常出现。

4、Time_wait过多有什么危害?

内存资源占用、端口资源占用(一个TCP连接至少消耗一个端口),每端口,无法建立新连接。 服务器资源受限:服务器监听一个端口,会把连接丢给线程处理,可以继续监听端口,但是线程池处理不了那么多连接。

5、Time_wait状态过多的优化?

什么时候产生: 首先调用close()发起主动关闭的一方,再发送最后一个ACK之后。

为何产生: 确保最后一个ACK到达,保证TCP全双工连接可靠释放;使旧的数据包过期消失。什么时候会产生大量Time_wait:当请求量比较大的时候,而且所有的请求都是短连接的时候。

如何避免: 多IP增加随机端口;内核参数调优(服务器设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIME_WAIT状态时可以重用端口)﹔使用长连接(Connection: keep- alive) 、 Linux参数net.ipv4.tcp_tw_reuse和 tcp_timestamps开启,复用处于TIME_WAIT的socket为新的连接所用。


如果我的文章对你有帮助的话,欢迎一键三连!!!

在这里插入图片描述