面试官:你再问TCP三次握手,我就要报警了!
CP三次握手和四次挥手,是面试官最爱问的“开场白”之一
别看它基础,真要讲清楚细节,分分钟让你冷汗直流!
这玩意儿就跟程序员相亲一样:
表面上问的是“你老家哪的”
实际上是在试探你有没有房、有没有车、能不能落户!
很多同学一开始答得头头是道
结果面试官一连追问:“为啥不是两次握手?”、“TIME_WAIT干啥的?”、“为啥挥手要四次?”
很多小伙伴在面试中可能会这样答:
“三次握手就是客户端和服务端互相确认连接状态的过程,四次挥手则是断开连接时双方分别关闭各自的发送通道。”
答案对吗?当然对!
但距离面试官的期望,可能还差那么一点点……
记住:简单的回答只是开始,深入的探讨才是关键。
其中有较多的细节问题,本篇文章全部会详细讲解!
接下来,希望大家能带着思考进行阅读,收获会更大。
Part1什么是TCP协议
你可以把它理解成互联网世界里的“老司机”
专治各种不可靠网络环境下的数据传输问题
它的口号是:“我不光要传得出去,还得收得回来!
简单来说,TCP 是一种 面向连接、可靠传输 的协议
它存在的意义,就是为了在那些“信号差、丢包多、谁都不靠谱”的网络环境下,给我们提供一条稳定、有序、不丢包、不乱序的数据传输高速公路。
Part2那TCP中的面向连接呢?
就是说,在真正开始传数据之前
客户端和服务器必须先坐下来“握个手”,确认彼此都在线、都能听懂对方说话
不能像发短信一样发完就走,也不管对方有没有收到。
就像相亲前先加个微信聊两句
总比直接上门提亲强吧?
Part3可靠传输?怎么保证?
- 数据是按顺序传送的,不会前面的还没到,后面的先来了;
- 每次传数据都会做校验,要是数据在路上被“压碎”了,接收方立马能发现;
- 如果发现丢了包、错乱了,TCP 就会自动重传、排序,直到你满意为止。
总之,TCP 的原则是:宁可慢一点,也不能错一点!
Part4那三次握手又是干嘛的?
为了建立这个“稳如老狗”的连接
TCP 设计了一个流程叫 三次握手
目的很简单:确认双方的发送和接收能力都没毛病!
第一次握手:客户端 → 服务端
客户端主动发起请求,发送一个 SYN=1 的报文(同步标志),告诉服务端:“我想和你建立连接。”
同时指定了自己的初始序列号 seq=x。
这个 SYN 报文不能携带数据,但它会消耗一个序号。
此时客户端进入 SYN_SENT 状态,
心里默念:“我在等你回消息……”
第二次握手:服务端 → 客户端
服务端收到 SYN 后,表示收到了连接请求,于是它也回一个 SYN=1,并带上自己的初始序列号 seq=y。
同时,把客户端的 seq+1 作为确认号 ack=x+1,
意思是:“我已经收到了你的SYN,老铁。”
这次回应中 SYN=1,ACK=1,
服务端进入 SYN_RCVD 状态,
心想:“兄弟挺有诚意,我也回应一波。”
第三次握手:客户端 → 服务端
客户端收到服务端的 SYN 后,再回一个 ACK=1 的确认报文,
将服务端的 seq+1 作为确认号 ack=y+1,
表示:“我也收到了你的SYN,咱们可以正式开始了!”
这时候客户端进入 ESTABLISHED 状态,
而服务端在收到这个 ACK 后,也进入 ESTABLISHED 状态,
连接就正式建立了!
用大白话来讲就是:
第一次:客户端喊一声:“喂,你能听到吗?”
第二次:服务端回应:“我能听到,你能听到我吗?”
第三次:客户端再确认:“我也能听到,咱们可以开始了。”
这三轮对话一过,连接就算正式建立了
接下来就可以放心传数据了,不怕断、不怕乱、不怕丢!
Part5要三次握手?两次不行吗?
🤔 先问个问题:为啥非得三次握手?
三次握手的目的很简单:
确保双方的发送和接收能力都正常,并且同步初始序列号(ISN),为后续可靠传输打基础。
🚀 那两次握手行不行呢?
想象一下这个场景:
- 客户端发出连接请求
- ,但因网络原因,第一个请求报文丢了。
- 客户端再发一次请求,这次服务端收到了并且回应了确认。
- 数据传输完毕后,连接释放了。
- 然而,之前丢失的那个请求报文在某个角落里“冬眠”了一段时间,突然醒了,又飘到了服务端。
- 服务端以为这是个新的连接请求,于是又开始建立连接,结果发现客户端不理它……
这就导致了一个问题:服务端一直在等待一个永远不会来的数据包,浪费了资源!
因此,两次握手不够保险,必须通过第三次握手来确保双方的状态都正常。
Part6什么是半连接队列?
🔍 半连接队列是啥?
当服务端第一次收到客户端的 SYN 请求时,会进入 SYN_RCVD 状态。
此时,连接还没完全建立,服务端会把这种状态下的请求放在一个队列中,这就是半连接队列。
还有个全连接队列,用于存放已经完成三次握手的连接。
如果队列满了,可能会导致丢包现象。
⏳ 关于重传次数
服务端发送完 SYN-ACK 后,如果没收到客户端的确认,会进行重传。
每次重传的时间间隔通常会指数增长(如 1s, 2s, 4s, 8s……),直到超过系统规定的最大重传次数,才会从半连接队列中删除该连接。
Part7ISN 是固定的吗?
🔢 ISN 是动态变化的
ISN 是一个随时间变化的32位计数器,每4毫秒加1。
这样设计是为了防止旧的分组在网络中滞留太久,导致错误解释。
固定ISN的风险:
如果ISN是固定的,攻击者很容易猜出后续的确认号,从而发起攻击。
所以,ISN是动态生成的,以增加安全性。
Part8三次握手可以携带数据吗?
第三次握手是可以携带数据的,但第一次和第二次握手不可以。
为什么呢?
假如第一次握手可以携带数据,恶意攻击者就可以在 SYN 报文中放入大量数据,疯狂重复发送 SYN 报文,导致服务器花费大量时间和内存处理这些垃圾报文。
- 第一次握手不能带数据
- :防止恶意攻击。
- 第三次握手可以带数据
- :因为此时客户端已处于 ESTABLISHED 状态,知道服务端的接收和发送能力正常。
Part9什么是 SYN 攻击?
⚔️ SYN 攻击是什么?
SYN 攻击是一种典型的 DoS/DDoS 攻击手段。
攻击者会在短时间内伪造大量不存在的 IP 地址,向服务器发送大量的 SYN 请求。
服务端回复确认包后,等待客户端确认,但由于源地址是伪造的,这些确认包永远不会到达,导致服务端资源被占用。
🚨 如何检测和防御?
检测:
你可以使用以下命令来检测是否遭受 SYN 攻击:
netstat -n -p TCP | grep SYN_RECV
如果你看到大量来自随机IP地址的半连接状态,基本上可以断定你正在遭受 SYN 攻击。
防御方法:
- 缩短超时时间(SYN Timeout)
- :减少等待确认的时间。
- 增加最大半连接数
- :提高系统承受能力。
- 过滤网关防护
- :设置防火墙规则,限制异常流量。
- SYN cookies技术
- :通过算法生成SYN-ACK响应,减少对内存资源的依赖。
Part10TCP 四次挥手
如果说三次握手是“确认关系”,那四次挥手就是“和平分手”。
TCP 是个很讲究感情的协议,它不会像 UDP 那样“说完就走”,而是要一步步说清楚:“我要走了”、“我知道你要走了”、“我也要走了”、“收到,拜了个拜。”
整个过程就像情侣分手一样,不是一句话就能搞定的。
而且,分手之后还要等一会儿才能彻底断开连接,这叫“2MSL 等待状态”。
💬 初始状态:
- 客户端和服务端都处于 ESTABLISHED 状态;
- 假设客户端想主动关闭连接,开始四次挥手。
第一次挥手:客户端 → 服务端
客户端发了一个 FIN 报文,表示:“我不再传数据了。”
此时客户端进入 FIN_WAIT1 状态。
FIN = 1,seq = u
不再发送新数据,但仍可以接收数据。
第二次挥手:服务端 → 客户端
服务端收到 FIN 后,马上回一个 ACK 报文,表示:“我知道你要走了。”
并把客户端的序号+1作为确认号返回。
此时服务端进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT2 状态。
ACK = 1,ack = u+1,seq = v
这时候只是单向关闭,服务端还能继续发数据给客户端。
第三次挥手:服务端 → 客户端
服务端处理完自己的数据后,也准备关闭连接,于是也发一个 FIN 报文,表示:“我也要走了。”
此时服务端进入 LAST_ACK 状态。
FIN = 1,ACK = 1,seq = w,ack = u+1
第四次挥手:客户端 → 服务端
客户端收到服务端的 FIN 后,回一个 ACK 报文,表示:“收到啦,咱们正式拜拜。”
然后客户端进入 TIME_WAIT 状态,等待一段时间后才真正关闭连接。
服务端收到 ACK 后直接进入 CLOSED 状态。
ACK = 1,ack = w+1,seq = u+1
客户端必须等 2MSL 时间才能彻底关闭连接。
Part11挥手为什么需要四次?两次不行吗?
因为 TCP 是全双工通信,双方都能发数据
所以不能像三次握手那样“合并发送”。
举个栗子🌰:
当服务端收到客户端的 FIN 时
它可能还有数据没发完,不能立马断开连接
只能先回一个 ACK 表示“我收到了”
等自己数据发完了,再发一个 FIN 表示“我也好了”。
所以中间这个缓冲期,就导致了四次挥手。
如果强行合并成两次,那就可能出现一边关了另一边还在傻等的情况
这就叫“半死不活”的连接,系统资源浪费严重!
Part12TIME_WAIT状态为什么要等2MSL?
这个“2MSL”听起来很神秘,其实它就是:
Maximum Segment Lifetime 的两倍时间,也就是报文段在网络中能存活的最长时间。
比如 MSL 是 60 秒,那 2MSL 就是 120 秒。
为啥要等这么久?
两个关键原因:
✅ 1. 确保最后一个 ACK 能到达服务端
万一客户端发的这个 ACK 丢了怎么办?
服务端会因为没收到 ACK 而重新发 FIN
这时候客户端还没彻底关闭,就能重新响应 ACK
避免服务端一直卡在 LAST_ACK 状态
如果不等 2MSL,客户端一发完就跑路
服务端收不到确认,就会无限重传,直到超时
✅ 2. 防止旧连接的报文干扰新连接
假设客户端不等 2MSL,直接关闭连接
而某个老报文在路上绕了一圈又回来了
这时候刚好有个新连接使用了同样的五元组(IP + 端口)
那这个老报文就可能被当成新连接的数据处理,造成混乱。
等 2MSL 的目的,就是让网络上所有属于旧连接的报文都“寿终正寝”
这样新连接就不会误判这些“幽灵报文”
为帮助理解 TCP 连接建立(三次握手)与终止(四次挥手)的状态变化,特提供一张典型的状态变迁图(见下图)。其中,粗实线箭头指示客户端的标准状态变迁,粗虚线箭头指示服务器端的标准状态变迁。
Part13TIME_WAIT 和 CLOSE_WAIT
TCP 中的两个“尴尬状态”:TIME_WAIT 和 CLOSE_WAIT
这两个状态可以说是 TCP 通信中的“分手后遗症”
一个是因为分手太慢被卡住(TIME_WAIT)
另一个是因为对方迟迟不分手而僵持(CLOSE_WAIT)
下面我来给你讲清楚它们到底啥意思、为啥会出问题、怎么解决!
🕒 TIME_WAIT 状态 —— 主动分手者的等待期
“我说拜拜了,你确定收到没?再等一会儿我才彻底走。”
✅ 它是谁触发的?
由主动关闭连接的一方触发。比如客户端调用了 close() 或 shutdown(),它就进入了“分手流程”。
🤔 它为什么存在?
主要是为了两个目的:
- 确保最后一个 ACK 能到达服务端
如果这个 ACK 丢了,服务端就会重传 FIN,客户端还能响应。 - 防止旧连接的报文干扰新连接
报文在网络里最多活 MSL 时间,等 2MSL 就能保证这些“幽灵包”都消失了。
MSL 是 Maximum Segment Lifetime,即报文段的最大生存时间,一般是 30s~60s。
所以 TIME_WAIT 的持续时间就是 2 × MSL,大约 60s 到 120s 不等。
⚠️ 它的问题是什么?
- 占用本地端口号资源;
- 大量 TIME_WAIT 可能导致端口耗尽;
- 对高并发短连接服务器影响较大(如 Web 服务器);
🔧 怎么解决大量 TIME_WAIT?
可以用 setsockopt 设置套接字选项,允许地址和端口复用:
int opt = 1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
这样就能避免因为 TIME_WAIT 导致的端口占用问题啦!
🐌 CLOSE_WAIT 状态 —— 被动分手者的拖延症
“你说要走了,我也收到了,但我还没关呢,你在等我……我在等你……”
✅ 它是谁触发的?
由被动关闭连接的一方触发。比如客户端先发 FIN,服务端回 ACK 后进入 CLOSE_WAIT。
🤔 它为什么存在?
表示服务端已经知道客户端要关闭连接了,但它还在等应用程序去处理剩下的数据,并手动关闭 socket。
⚠️ 它的问题是什么?
如果程序忘了调用 close() 去关闭 socket,
那这个连接就会一直卡在 CLOSE_WAIT,
最终导致连接数爆炸、资源泄漏、服务崩溃……
💡 常见原因:
- 忘记关闭 socket;
- 没有对 read/write 返回值做正确判断;
- 多线程/异步编程中未及时释放资源;
🔧 怎么排查和解决?
可以使用如下命令查看当前系统的 CLOSE_WAIT 数量:
netstat -antp | grep CLOSE_WAIT
一旦发现很多 CLOSE_WAIT,就要检查代码有没有漏掉 close() 或者异常退出没有清理资源。
总结
TCP 的连接和断开,看似只是几个来回的数据包,但背后却藏着网络通信中最核心的设计理念:可靠传输、资源管理、防止混乱。
从“三次握手”到“四次挥手”,再到“TIME_WAIT”和“CLOSE_WAIT”,这些状态不是为了难为我们,而是为了让整个互联网更稳定、更安全地运行。
作为开发者,理解它们不仅有助于应对面试官的灵魂拷问,更重要的是能在实际工作中排查问题、优化系统、写出更健壮的网络程序!
📌 如果你是初学者:建议动手抓个包看看三次握手和四次挥手的实际过程(Wireshark 走起);
📌 如果你是进阶者:不妨研究下 TCP 的保活机制、端口复用、以及高并发下的连接管理;
📌 如果你是面试党:记住一句话——能画图、能举例、能说出坑在哪,才是真掌握!
📢面试中TCP高频面试题部分列举
- 请画出三次握手和四次挥手的示意图
- 为什么连接的时候是三次握手?
- 什么是半连接队列?
- ISN(Initial Sequence Number)是固定的吗?
- 三次握手过程中可以携带数据吗?
- 如果第三次握手丢失了,客户端服务端会如何处理?
- SYN攻击是什么?
- 挥手为什么需要四次?
- 四次挥手释放连接时,等待2MSL的意义?
- TCP和UDP的主要区别是什么?
- TCP是如何保证可靠传输的?(关键机制)
- TCP的滑动窗口机制是什么?它解决了什么问题?(流量控制)
- TCP的拥塞控制算法主要有哪些阶段?描述其基本原理。(慢启动、拥塞避免、快重传、快恢复)
- 什么是TCP的粘包和拆包问题?为什么会出现?如何解决?
- TCP的Keepalive机制是什么?它的作用是什么?有什么缺点?与应用层心跳有何区别?
- 描述TCP的状态机(重点描述连接建立和关闭过程中的状态变迁)。
- 为什么要有TIME_WAIT状态?过多的TIME_WAIT状态有什么影响?如何优化?
- TCP头部结构包含哪些关键字段?(至少说出6-8个并说明作用)
- TCP的最大报文段长度(MSS)是什么?它是如何确定的?与MTU有什么关系?
- 什么是Nagle算法?它的目的是什么?在什么场景下可能需要关闭它?
- 什么是延迟确认(Delayed ACK)?它的目的是什么?
- TCP真的100%可靠吗?(可靠性的边界)
- MTU和MSS有什么区别?
- TCP如何检测和处理丢包?(超时重传 vs 快速重传)
- TCP连接建立后,如果通信双方一直不发送数据,连接会一直保持吗?(涉及Keepalive和中间设备超时)
往期推荐
【大厂标准】Linux C/C++ 后端进阶学习路线
C/C++ 高频八股文面试题1000题(一)
C/C++ 高频八股文面试题1000题(二)
点击下方关注【Linux教程】,获取编程学习路线、项目教程、简历模板、大厂面试题、大厂面经、编程交流圈子等等。