欢迎光临
我们一直在努力

面试官:TCP 为什么要三次握手与四次分手?

一、开场:这道题到底在考什么

很多求职者在被问到「TCP 为什么要三次握手、为什么要四次分手」时,第一反应是把三次握手、四次挥手的过程背一遍:客户端先发 SYN,服务端回 SYN+ACK,客户端再回 ACK……背是背下来了,可面试官一旦追问「为什么是三次,两次行不行」「为什么挥手要四次,三次不行吗」「TIME_WAIT 为什么要等 2MSL」,很多人就卡壳了。

其实这道题表面考的是「过程」,背后考的是一连串更深入的问题:你对 TCP 协议的设计目标理解得有多深?你能不能从「可靠传输」这个根本需求出发,一步一步推导出协议为什么必须这样设计?你对网络世界的不可靠性、延迟、丢包、乱序、重复这些现实约束有没有概念?你是否知道这些设计在生产环境里会带来哪些真实问题,以及如何排查和优化?

本篇文章会从零开始,把 TCP 连接的建立与释放讲透。全文会覆盖以下内容:TCP 是什么、报文段长什么样、三次握手每一步发生了什么、为什么必须是三次而不是两次或四次、四次挥手每一步发生了什么、为什么挥手必须四次、TIME_WAIT 为什么要等 2MSL、常见的面试追问怎么答,以及用抓包工具亲眼看到这些过程。文章比较长,建议配合一杯水慢慢看,看到最后,这道题你将可以从「背诵选手」升级为「推导型选手」。

另外先说明一个用词上的小习惯:面试官口中常说的「四次分手」,在技术文档和教材里的标准叫法是「四次挥手」。两者指的是同一个过程,也就是 TCP 连接的释放。本文标题保留「四次分手」这个口语说法,正文统一使用标准术语「四次挥手」。

二、地基:先理解 TCP 是什么、解决什么问题

要理解三次握手和四次挥手,必须先理解 TCP 的存在意义。TCP(Transmission Control Protocol,传输控制协议)是位于传输层的一种协议。它的下层是 IP(Internet Protocol,网络层协议),IP 负责把数据报从一台主机尽力送到另一台主机;它的上层是应用层,也就是 HTTP、FTP、SMTP、SSH 这些我们每天都在使用的协议。

IP 有一个著名的特点:尽力而为(best-effort)。IP 不承诺数据一定能到达,不承诺到达的顺序,不承诺数据不会重复,也不承诺数据的内容不会被破坏。也就是说,IP 层提供一个「无连接、不可靠」的传输服务。而互联网上的绝大多数应用,比如我们打开网页、下载文件、发邮件、登录服务器,都需要一个可靠、有序、完整的数据流。TCP 就是架在不可靠的 IP 之上,通过软件手段为上层应用「包装」出一条可靠的数据传输通道的协议。

TCP 提供的主要能力包括:面向连接、可靠交付、按序交付、流量控制、拥塞控制、全双工通信。其中「面向连接」是本文的主角。所谓面向连接,指的是通信双方在正式传输数据之前,必须先建立一段逻辑上的连接关系,双方为这段连接分配好资源、确认好彼此的初始状态,之后才能开始收发数据;数据传输结束后,还需要显式地释放这段连接,释放占用的资源。

为什么需要一个「连接」?因为 TCP 要把不可靠的 IP 服务改造成可靠的字节流服务,就必须维护大量的状态信息,比如:我下一个要发送的字节序号是多少、我期望对方下一个发来的序号是多少、对方的接收窗口有多大、哪些数据已经确认、哪些数据还需要重传。这些状态信息必须是「对称」的——通信双方各自维护自己的一份,并且在正式通信前要让对方知道自己这方的初始状态。这个过程,本质上就是握手要完成的事情。

还要强调一点:TCP 的连接是「端到端」的,是通信两端主机上各自软件维护的抽象状态,中间的路由器、交换机并不感知 TCP 连接的存不存在,它们只负责转发 IP 数据报。所以「建立连接」并不等于在网络上真的拉了一根物理专线,而是两端各自进入「我认为我和对方建立了一条可靠通道」的状态,并且双方对这个状态的认知达成了一致。

理解了这些,再看四周挥手就容易了:三次握手是「双方就连接的建立达成一致」,四次挥手是「双方就连接的拆除达成一致」。而「达成一致」在网络这种不可靠环境下,是件远比想象中复杂的事。接下来我们先看 TCP 报文段的结构,因为后续每一步握手挥手都要精确描述报文里的标志位和序号。

三、TCP 报文段结构:每次挥手握手你看到的那些字段

TCP 通信的基本单位叫「报文段」(segment)。一个 TCP 报文段通常由「TCP 首部」和「数据」两部分组成。TCP 首部的长度不固定,默认是 20 字节,如果携带选项可以更长。下面是 TCP 首部的主要字段,我们按顺序过一遍,重点关注跟握手挥手有关的几个。

源端口(Source Port)和目标端口(Destination Port):各占 16 位,用来标识发送方和接收方的应用进程。一个 TCP 连接可以由四元组唯一标识:源 IP、源端口、目标 IP、目标端口。四次挥手里我们反复强调「连接的唯一性」,指的就是这个四元组。

序号(Sequence Number):占 32 位。它表示本报文段所携带数据的第一个字节在整个字节流中的编号。TCP 把应用层交下来的数据看成一条连续的字节流,每一个字节都编一个号。例如一个报文段的序号是 1001,携带了 100 字节数据,那么下一个报文段的序号就应该是 1101。握手阶段虽然没有应用数据,但 SYN 报文也要「消耗」一个序号,这个细节非常重要,后面会反复出现。

确认号(Acknowledgment Number):占 32 位。它表示「期望收到对方下一个报文段的序号」。例如我收到了对方序号为 1001、长度为 100 的数据,确认号就是 1101,意思是「1001 到 1100 我都收到了,你下次从 1101 开始发」。确认号只有在 ACK 标志位为 1 的时候才有意义。

数据偏移(Data Offset):占 4 位,表示 TCP 首部占多少个 32 位字,也就是首部长度。有了它才知道数据从哪里开始。

保留位(Reserved):占 6 位,保留给将来使用,通常为 0。

六个控制标志位:这是握手机制的核心,逐个说明如下。

  • URG:紧急指针有效,表示报文段中含有紧急数据,较少使用。
  • ACK:确认字段有效。绝大多数报文段都会置 ACK,只有第一个 SYN 报文段不置 ACK。
  • PSH:督促接收方尽快把数据交给应用层,不要攒在缓冲区里。
  • RST:重置连接。当出现严重错误、连接异常时,用 RST 强制拆除连接。比如服务端收到一个针对不存在端口的 SYN,就会回一个 RST。
  • SYN:同步序号,用于建立连接。SYN 报文会携带一个初始序号。
  • FIN:结束发送,表示发送方已经没有数据要发送了,请求释放连接。

窗口大小(Window):占 16 位,用于流量控制,告诉对方自己接收缓冲区还能容纳多少字节,防止发送方发得太快把接收方淹没。

校验和(Checksum):占 16 位,对整个报文段做校验,接收方用它判断报文段在传输过程中有没有出错。

紧急指针(Urgent Pointer):占 16 位,仅在 URG 标志位置位时有效。

选项(Options):长度可变,最长可达 40 字节。常见的有 MSS(最大报文段长度)、SACK(选择性确认)、窗口扩大因子、时间戳等。三次握手阶段,双方会在 SYN 里互相通告自己的 MSS 和窗口扩大因子,这些协商数据正是握手阶段才交换的「连接参数」。

了解了这些字段之后,握手挥手的过程就非常直观了:SYN、ACK、FIN 等标志位加上精心维护的序号和确认号,构成了 TCP 建立和拆除连接的完整「对话协议」。下面我们就进入正题,先看三次握手。

四、三次握手完整过程:一个包都不能少

三次握手(three-way handshake)是 TCP 建立连接时必须经历的过程。它由三个报文段组成,通常称为「第一次握手」「第二次握手」「第三次握手」。下面我们以最常见的「客户端主动连接服务端」场景为例,逐步拆解。假设客户端是 C,服务端是 S。

4.1 第一次握手:客户端发送 SYN

客户端应用调用 connect() 之后,操作系统会构造一个 TCP 报文段并发送给服务端。这个报文段的关键特征是:SYN 标志位置 1,ACK 标志位为 0,同时携带一个客户端随机生成的初始序号,记为 seq=x。这个 x 通常不是从 0 或 1 开始,而是操作系统用一个基于时间的算法生成的一个随机值,目的是防止历史连接中的旧报文段与新建连接混淆。

发送完 SYN 之后,客户端进入 SYN_SENT 状态,等待服务端的确认。如果服务端迟迟没有应答,客户端会启动重传机制,按指数退避的方式反复重发 SYN,直到超时或收到应答。在 Linux 上,SYN 重传次数由内核参数 net.ipv4.tcp_syn_retries 控制。

4.2 第二次握手:服务端回复 SYN + ACK

服务端收到客户端的 SYN 之后,如果愿意建立连接,就会回复一个报文段。这个报文段同时做了两件事:一是「确认」收到了客户端的 SYN,因此 ACK 标志位置 1,确认号 ack=x+1;二是「发起」自己这一侧的同步,因此 SYN 标志位也置 1,并携带服务端自己随机生成的初始序号 seq=y。所以这一个报文段通常写作 SYN+ACK。

注意 ack=x+1 里的这个 +1:客户端发来的 SYN 报文虽然不携带应用数据,但 SYN 本身要消耗一个序号。所以服务端对 SYN 的确认是 x+1,表示「你发来的 x 我收到了,你下次从 x+1 开始发」。这个规则在四次挥手中对 FIN 同样适用,是面试高频考点。

发送完 SYN+ACK 之后,服务端进入 SYN_RCVD 状态。此时服务端的连接尚未完全建立,它在等待客户端的第三个报文。值得注意的是,服务端从收到 SYN 到确认连接建立这段时间里,半连接(half-open)状态会占用一部分内核资源,于是就有了著名的 SYN Flood 攻击,这个我们后面单独讲。

4.3 第三次握手:客户端回复 ACK

客户端收到服务端的 SYN+ACK 之后,确认服务端的 SYN 序号 y 和自己的 SYN 序号 x 都被接受了,于是回复最后一个报文段:ACK 标志位置 1,确认号 ack=y+1,序号 seq=x+1。注意这个报文段的 SYN 标志位为 0,它是纯确认报文。

这个 ACK 报文发送出去之后,客户端就认为连接已经建立,随即进入 ESTABLISHED 状态。服务端收到这个 ACK 之后,也进入 ESTABLISHED 状态。此时双方都认为连接完全建立,可以开始传输应用数据了。

4.4 三次握手中的状态流转汇总

为了便于记忆,我们把两端的状态变化归纳成下面这张表。

阶段客户端状态服务端状态关键报文
初始 CLOSED LISTEN 无
第一次握手后 SYN_SENT LISTEN 客户端 → 服务端:SYN(seq=x)
第二次握手后 SYN_SENT SYN_RCVD 服务端 → 客户端:SYN+ACK(seq=y, ack=x+1)
第三次握手后 ESTABLISHED ESTABLISHED 客户端 → 服务端:ACK(seq=x+1, ack=y+1)

从这张表可以清楚看到「三次」的由来:客户端一次、服务端一次、客户端再确认一次。那么问题来了:为什么刚好三次?两次行不行?四次行不行?这正是下一章要深入讨论的核心。但在讨论之前,我们先花一点篇幅解释一个重要概念——初始序列号 ISN,因为它是理解握手安全性的基础。

五、初始序列号 ISN:为什么不能简单从 0 开始

在三次握手中,双方各自随机生成一个初始序列号,称为 ISN(Initial Sequence Number)。这个设计看起来麻烦,其实大有深意。如果每次连接的初始序号都从 0 开始,会发生什么?

想象这样一个场景:客户端和服务端之间曾经建立过一条连接,传输了一些数据,然后这条连接被关闭了。但网络上可能还残留着这条旧连接发出的、由于网络拥塞而延迟很久才到达的报文段。过了一段时间,同样的客户端和服务端、同样的端口建立起一条新连接。如果新连接的初始序号仍然从 0 开始,那么那条延迟到达的旧报文段,其序号恰好落在新连接的合法接收窗口内,接收方就会把它当成新连接的数据收下来,导致数据串扰。这就是所谓的「旧连接报文段污染新连接」问题。

为了避免这种问题,TCP 要求连接的初始序号随着时间推移而不断增加,并且要有足够的随机性,使得旧连接残留报文段的序号几乎不可能恰好落在新连接的接收窗口内。实现上,典型做法大致是:ISN = 某个基准值 + 基于时间和随机数的扰动。Linux 等现代操作系统会额外加入加密散列等机制,保证 ISN 难以被外部预测。

ISN 的不可预测性还有一个重要的安全意义:防止序列号预测攻击。设想一个攻击者如果能够预测目标主机下一次连接的 ISN,他就可以伪造一个合法的 TCP 报文注入到受害者的连接中,劫持会话或投毒数据。历史上确实发生过利用可预测 ISN 发起的 TCP 劫持攻击,因此现代实现都把 ISN 随机化作为一条基本安全要求。

理解了 ISN,就能理解为什么握手阶段要交换序号、为什么要确认对方的序号加一。接下来我们回答本文第一个核心问题:为什么必须是三次。

六、为什么是三次握手:从「可靠性」反推设计

这是整篇文章的第一处高潮。要真正理解「为什么三次」,必须站在一个非常原始的问题面前:在不可靠的网络中,通信双方如何就「连接已建立」这件事达成一致?

6.1 先回答最朴素的问题:为什么需要握手?

有人会问:为什么一定要握手?客户端直接发数据不行吗?确实有一种方案叫「直接发数据」,但 TCP 之所以要握手,是因为双方在传输数据之前需要交换一些关键信息:

  • 双方都要确认「对方真的存在、并且愿意和我通信」。
  • 双方都要交换初始序列号,用于后续的可靠传输和按序重组。
  • 双方都要协商一些连接参数,比如 MSS、窗口扩大因子、是否启用 SACK、是否启用时间戳等。
  • 双方都要为这条连接分配接收缓冲区等资源,而这些资源只有在确认连接成立后才值得分配。

如果不握手,直接发数据,那么数据报文到达一个没有准备好接收的对端时,只能被丢弃或者触发 RST,效率和可靠性都无从谈起。因此,「握手」是用来完成「初始化」的必要环节,这一点争议不大。真正的争议在于「为什么刚好三次」。

6.2 两次握手为什么不够:被遗忘的「服务端的确认」

很多人第一反应是:客户端发一个 SYN,服务端回一个 SYN+ACK,连接不就建立了吗?这不就是两次握手吗?为什么还要客户端再发一个 ACK?

答案藏在网络延迟里。考虑这样一个现实问题:报文的延迟可能非常长。客户端发出的 SYN 可能因为在某个路由器上排队、或者走了绕路,延迟很久才到达服务端;也可能客户端自己超时后重发一个 SYN,而原来那个「慢悠悠」的 SYN 后来才到达。如果采用「两次握手」——服务端收到 SYN 就立刻认为连接建立——会发生什么?

情况一:失效的 SYN 让服务端白等。客户端发了一个 SYN,自己超时了,于是放弃这次连接(或者应用取消了连接)。但这个 SYN 可能并没有丢,只是在网络上堵了一会儿,最终它还是到达了服务端。服务端执行两次握手逻辑,回了一个 SYN+ACK,并认为自己进入了 ESTABLISHED,分配了接收缓冲区。可客户端这边早就放弃了,根本不会再理会这个 SYN+ACK,也不会向服务端发数据。于是服务端就白白维护着一个「自以为已经建立」的连接,直到超时或收到 RST 才发现对方根本不在。这就是两次握手的第一个致命缺陷:服务端在无法确认客户端是否真的愿意建立连接的情况下,就单方面分配了内核资源。

情况二:重复 SYN 造成服务端重复开连接。还有一种更常见的情形:客户端发出 SYN 后迟迟没有等到 SYN+ACK,于是超时重发了一个 SYN;结果原来那个 SYN 和新的 SYN 都到达了服务端。如果服务端每收到一个 SYN 就认为「连接建立」,它会为两个 SYN 分别建立两条连接、各回一个 SYN+ACK。而客户端只想要一条连接,最终多余的那条连接又被服务端一直占着。可见,「服务端只要收到 SYN 就单方面完成建立」在不可靠网络下必然带来资源浪费和状态错乱。

两次握手的根本问题在于:它在「连接建立」这件事上,只确认了客户端的意愿,没有给客户端一个机会去确认「服务端也已经准备好」。连接是双向的,建立连接的共识也必须是双向确认的。

6.3 为什么刚好三次:确认不能再少,也不能再多

三次握手恰好补上了这个缺口:第一次是客户端说「我要建立连接,我的初始序号是 x」;第二次是服务端说「我收到了,我的初始序号是 y,我确认你的 x」;第三次是客户端再说「我收到了你的 y,也确认你准备好了」。经过这一次来回,双方都确认了三件事:对方存在、对方的初始序号已知、对方已经知道自己准备好了。

那么四次行不行?理论上是可行的,比如客户端再发一个 ACK 去确认「我收到了你对我的确认」。但这一步在信息上是冗余的:第三次握手的 ACK 已经完成了双方的互相确认,再多一个报文只会增加一次往返时延,却不能带来新的可靠性收益。因此 TCP 选择三次,是在可靠性与效率之间取得的最优解。两次不够,三次正好,四次浪费。

七、四次挥手完整过程:一次也不能少

连接的建立是「双方确认都准备好了」,而连接的释放则复杂得多:因为 TCP 是全双工的,客户端和服务端各自都有独立的数据发送通道。建立连接时可以一次握手同时交换双方的 SYN,但拆卸连接时,通常一方已经说完、另一方可能还有数据要发,所以两个方向的关闭必须分开进行。这就是四次挥手的基本出发点。

7.1 第一次挥手:主动关闭方发送 FIN

假设客户端先完成数据发送,调用 close() 发起关闭。客户端会发送一个 FIN 报文:FIN 标志位置 1,ACK 标志位为 1,序号 seq=u(这里 u 表示此前已经发送完毕的数据之后的下一个序号)。FIN 的含义是:「我已经没有数据要发给你了,但我仍然愿意接收你发来的数据。」发送完 FIN 后,客户端从 ESTABLISHED 进入 FIN_WAIT_1 状态。

7.2 第二次挥手:对端回复 ACK

服务端收到 FIN 后,知道客户端不再发送数据,于是回复一个 ACK:ACK 标志位置 1,确认号 ack=u+1,表示「你的 FIN 我收到了」。注意,这里服务端只确认了客户端的关闭请求,但服务端自己可能还有未发完的数据,所以暂时不发送 FIN。发送完 ACK 后,服务端进入 CLOSE_WAIT 状态,客户端在收到这个 ACK 后进入 FIN_WAIT_2 状态。

CLOSE_WAIT 的意义在于:服务端应用层知道对方要关闭了,但自己还可以继续把剩余的数据发送完。这时连接处于「半关闭」(half-close)状态,一个方向已经关闭,另一个方向仍然开放。

7.3 第三次挥手:对端发送 FIN

等到服务端把剩余数据都发送完毕,也调用 close() 关闭自己这一侧,于是向客户端发送 FIN:FIN 标志位置 1,ACK 标志位为 1,序号 seq=w(服务端已发送数据的下一个序号),确认号 ack 通常仍为 u+1。发送完 FIN 后,服务端从 CLOSE_WAIT 进入 LAST_ACK 状态,等待客户端做最后确认。

7.4 第四次挥手:主动关闭方回复 ACK

客户端收到服务端的 FIN 后,回复最后一个 ACK:ACK 标志位置 1,确认号 ack=w+1,表示「你的 FIN 我也收到了」。客户端发送完这个 ACK 后,并不立即进入 CLOSED,而是进入 TIME_WAIT 状态并等待 2MSL。服务端收到这个 ACK 后,直接进入 CLOSED 状态,连接正式释放。

7.5 四次挥手状态流转汇总

为了便于记忆,我们把两端的状态变化整理成下表。

阶段客户端状态服务端状态关键报文
初始 ESTABLISHED ESTABLISHED 无
第一次挥手后 FIN_WAIT_1 ESTABLISHED 客户端 → 服务端:FIN(seq=u)
第二次挥手后 FIN_WAIT_2 CLOSE_WAIT 服务端 → 客户端:ACK(ack=u+1)
第三次挥手后 TIME_WAIT LAST_ACK 服务端 → 客户端:FIN(seq=w, ack=u+1)
第四次挥手后 TIME_WAIT(等待 2MSL) CLOSED 客户端 → 服务端:ACK(ack=w+1)

从表中可以看到,四次挥手的「四」,本质上是两个方向的关闭各需要两次:一方发 FIN,另一方回 ACK;另一方再发 FIN,这一方再回 ACK。

八、为什么挥手必须四次:半关闭才是关键

这是整篇文章的第二处高潮。为什么握手可以三次搞定,挥手却要四次?能不能像握手那样把服务端的 ACK 和 FIN 合并成一个报文,变成三次挥手?

答案在于:握手时,双方的 SYN 都是「立即可以发出」的,客户端愿意建立连接的同时,服务端也可以马上表达自己愿意建立连接,所以服务端可以在同一个报文里同时回 SYN+ACK。但挥手时,服务端收到客户端的 FIN 并不代表服务端自己马上就没有数据要发了。服务端可能还有一批已经写入发送缓冲区的数据,需要继续发送完;应用层也可能还需要一段时间来处理收尾逻辑。因此在收到 FIN 之后,服务端通常会先回 ACK,告诉客户端「我知道你要关了」,然后继续把剩余数据发完,最后再单独发送自己的 FIN。

这个「先 ACK、再 FIN」的拆分,引出了 TCP 的一个重要设计:半关闭。TCP 允许连接的一方关闭自己这边的发送通道,同时保留接收通道。第二次挥手先关掉了「客户端到服务端」的发送方向,第三次挥手才关掉「服务端到客户端」的发送方向。如果强行把 ACK 和 FIN 合并,就等于要求服务端在收到 FIN 的那一刻必须立即停止发送,这会丢掉尚未发完的数据。

当然,协议并没有规定 ACK 和 FIN 绝对不能合并。如果服务端收到 FIN 时确实已经没有任何数据要发,应用层也立刻关闭,那么服务端完全可以在同一个报文里回 FIN+ACK,这时挥手就变成了三个报文。但在一般实践中,ACK 和 FIN 大多分开,因此教材统一讲「四次挥手」。

九、TIME_WAIT 为什么要等 2MSL

第四次挥手之后,主动关闭方进入 TIME_WAIT,而不是立即进入 CLOSED。这是四次挥手中最容易被忽略、也最常被追问的细节。

MSL 是 Maximum Segment Lifetime,即报文段在网络中的最大生存时间。典型实现中 MSL 约为 30 秒到 2 分钟,因此 TIME_WAIT 通常持续 1 到 4 分钟。等待 2MSL 主要有两个原因。

第一,保证最后一个 ACK 能够到达对端。如果客户端发出的最后一个 ACK 丢失了,服务端迟迟收不到确认,就会重发 FIN。此时客户端仍处于 TIME_WAIT,可以再次回 ACK。如果客户端第四次挥手后立即关闭,而最后一个 ACK 又恰好丢失,那么服务端重发的 FIN 就再也得不到回应,服务端会一直处于 LAST_ACK,无法正常释放连接。

第二,让旧连接残留的报文从网络中彻底消失。等待 2MSL 之后,这条连接期间发出的所有报文都有足够的时间被网络丢弃,不会残留到网络里。这样即使之后同样的四元组被复用建立新连接,旧连接延迟到达的报文也不会再污染新连接的数据。

TIME_WAIT 只出现在主动关闭方。正是因为主动关闭方承担了「确保对端收到最后 ACK」的责任。被动关闭方收到最后的 ACK 后可以直接进入 CLOSED,不需要 TIME_WAIT。

十、常见面试追问怎么答

理解了上述推导之后,面试里那些「连环追问」就可以从容回答了。下面整理几条高频问题及回答思路。

  • 为什么是三次握手,不是两次?两次握手的核心缺陷是服务端无法确认客户端是否真的准备好了。三次握手通过客户端的最后一次 ACK,完成了双向确认,避免了失效 SYN 和重复 SYN 造成的资源浪费。
  • 为什么三次正好,四次不行吗?三次已经完成了双向确认,第四个报文只能带来冗余的往返时延,不能显著增加可靠性,所以协议选择更高效的三次。
  • 为什么挥手要比握手多一次?因为 TCP 全双工,两个方向的关闭必须分别确认。服务端收到 FIN 后可能还有数据要发,通常必须先回 ACK、再发 FIN,因此至少四次。
  • 三次挥手行不行?如果服务端收到 FIN 时已经没有数据要发,ACK 和 FIN 可以合并为 FIN+ACK,此时实际上只需要三个报文。但一般情况无法保证这一点,所以标准描述是四次。
  • TIME_WAIT 为什么是 2MSL,而不是 1MSL 或 3MSL?2MSL 既覆盖「最后一个 ACK 丢失后对端重发 FIN」的一来一回,也保证旧报文彻底消亡。
  • SYN Flood 攻击是怎么回事?服务端收到 SYN 后进入 SYN_RCVD,会为半连接分配资源。攻击者伪造大量不同源地址的 SYN,让服务端资源耗尽。常见缓解手段有 SYN Cookie、缩短 SYN 重试次数、增大半连接队列等。

十一、用抓包工具亲眼看到全过程

纸上得来终觉浅。理解 TCP 最好的方式是亲手抓一次包。下面给出两个常用抓包工具的简单用法,帮助你在本机复现三次握手和四次挥手。

如果使用 tcpdump,可以在 Linux 或 macOS 上执行:

# 抓取本机与目标主机之间的 TCP 交互
sudo tcpdump -i any host example.com -nn -S

其中 -nn 表示不把端口和主机名反向解析成名称,-S 表示显示绝对序号,这样更像教材里讲的 seq、ack 逻辑。

如果使用 Wireshark,可以先启动抓包,然后在浏览器访问目标网站,最后用过滤器 tcp.port == 443 或 tcp.port == 80 缩小范围。在捕获列表里选中某条 TCP 报文,右键选择 Follow,再选择 TCP Stream,就能看到完整的三次握手、数据传输和四次挥手序列。

抓包时建议重点观察三件事:一是 SYN 报文只有 SYN 没有 ACK;二是 SYN+ACK 同时携带服务端的 ISN 和对客户端 SYN 的确认;三是挥手时主动关闭方最后进入 TIME_WAIT,而被动关闭方直接进入 CLOSED。亲眼看到这些,比背十遍概念都管用。

十二、总结:把「背诵」变成「推导」

回头看整篇文章,三次握手和四次挥手的每一个细节,都可以从「不可靠网络中的可靠通信」这个原始需求推导出来。

  • TCP 的目标,是在 IP 的尽力而为之上构造可靠、有序的字节流。
  • 握手是为了在正式通信前交换初始序号、协商参数、确认双方都准备好。
  • 三次握手是最小可行方案:两次不够,四次浪费。
  • 挥手必须把两个方向的关闭分开处理,所以通常需要四次。
  • TIME_WAIT 的 2MSL 等待,是对最后一个 ACK 和旧报文残留的双重保障。

以后再被问到「TCP 为什么要三次握手、为什么要四次挥手」,正确的打开方式不是从 SYN、SYN+ACK 背起,而是先回答:因为网络不可靠,因为连接是双向的,因为建立只需要双方确认一次,而拆除需要两个方向分别确认。把底层约束讲清楚,细节自然就顺理成章,面试官也会看到你和「背诵型选手」的区别。

赞(0)
未经允许不得转载:171主机测评 » 面试官:TCP 为什么要三次握手与四次分手?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址