TCP三次握手与四次挥手

TCP三次握手与四次挥手

TCP(传输控制协议)是TCP/IP协议簇的核心,核心特性是面向连接、可靠传输、全双工通信,而三次握手(建立连接)和四次挥手(释放连接)是TCP实现这些特性的关键机制。本文将彻底拆解三次握手与四次挥手的每一步逻辑、标志位含义、序列号/确认号变化,结合socket源码调用、异常场景(如SYN洪水、TIME_WAIT)、面试高频问题,从底层原理到实战应用,层层递进,帮你彻底吃透,轻松拿捏面试高分,避开开发中的坑!

一、前置核心知识

在拆解三次握手和四次挥手前,必须先掌握TCP的核心字段和基础概念,否则会陷入"看懂步骤,不懂原理"的误区,以下内容直接关联后续核心逻辑,重点记忆:

1.1 TCP核心标志位(握手/挥手的"信号")

TCP头部包含6个关键标志位(FLAGS),其中3个是握手/挥手的核心,其余3个辅助理解,具体如下:

  • SYN(同步位):用于发起连接,请求同步序列号,标识"我要和你建立连接,请你确认";

  • ACK(确认位):用于确认接收的数据,标识"我已经收到你的数据,下一次请发这个序列号之后的数据";

  • FIN(终止位):用于发起释放连接,标识"我这边没有数据要发送了,准备关闭连接";

  • 补充:RST(复位位,连接异常时使用)、PSH(推送位,立即推送数据)、URG(紧急位,紧急数据优先),本文重点聚焦前3个。

1.2 序列号(seq)与确认号(ack)(可靠传输的核心)

TCP是可靠传输,核心依赖"序列号+确认号"的机制,确保数据不丢失、不重复、按序到达,核心规则如下:

  • 序列号(seq):发送方发送数据的"编号",每发送一个字节,序列号+1;发起连接时,会随机生成一个初始序列号(ISN,Initial Sequence Number),后续数据的序列号基于ISN递增;

  • 确认号(ack):接收方对发送方的"回应",确认号 = 接收方已收到的最大序列号 + 1,表示"我已经收到你seq≤X的数据,请你下次发送seq=X+1的数据";

  • 关键原则:ACK标志位为1时,确认号才有效;SYN标志位为1时,序列号是初始序列号(ISN),此时确认号无效(ack=0)。

1.3 TCP连接的核心特性(关联握手/挥手逻辑)

  • 面向连接:必须先通过三次握手建立连接,才能传输数据,连接释放需通过四次挥手;

  • 全双工通信:连接建立后,双方可同时发送和接收数据(这也是四次挥手需要分两次关闭的原因);

  • 可靠传输:通过序列号、确认号、重传机制(超时重传、快速重传)确保数据可靠。

1.4 核心源码关联(socket调用与握手/挥手对应)

我们日常开发中调用的socket接口,本质是触发TCP的握手/挥手逻辑,对应关系如下(Java示例,贴合实战):

java 复制代码
// 服务端(被动建立连接)
ServerSocket serverSocket = new ServerSocket(8080); // 绑定端口,监听连接(LISTEN状态)
Socket socket = serverSocket.accept(); // 阻塞等待客户端连接(触发三次握手)

// 客户端(主动建立连接)
Socket socket = new Socket("127.0.0.1", 8080); // 发起连接(触发三次握手)

// 释放连接(双方均会调用,触发四次挥手)
socket.close(); // 调用close(),触发FIN包,发起释放连接
serverSocket.close();

关键:accept()new Socket()的底层,就是TCP三次握手的过程;close()的底层,就是TCP四次挥手的过程,后续拆解会对应关联。


二、TCP三次握手:建立可靠连接

三次握手的核心目的:双方确认彼此的发送和接收能力正常,同步初始序列号(ISN),建立可靠的全双工连接。很多人会疑惑"为什么是三次,不是两次或四次?",答案会在步骤拆解后详细说明,先看完整流程。

2.1 三次握手完整时序图

客户端(Client) → 服务端(Server),三步交互,核心标志位、seq/ack变化如下(重点记每一步的核心作用):

  1. 客户端 → 服务端:SYN=1,seq=ISN_C(客户端初始序列号),ack=0

    1. 服务端 → 客户端:SYN=1,ACK=1,seq=ISN_S(服务端初始序列号),ack=ISN_C + 1

    2. 客户端 → 服务端:ACK=1,seq=ISN_C + 1,ack=ISN_S + 1

2.2 逐步骤拆解(结合源码逻辑,吃透每一步)

假设客户端主动发起连接,服务端处于LISTEN状态(绑定端口后,通过listen()接口进入该状态),逐步解析每一步的作用、底层逻辑:

第一步:客户端发起连接(SYN包,握手发起)

客户端调用new Socket()时,底层会发送一个SYN包(同步包),核心细节:

  • 标志位:SYN=1,ACK=0(此时还未收到服务端数据,确认号无效);

  • 序列号:seq=ISN_C(随机生成的初始序列号,比如1000,不同系统生成规则不同,避免重复);

  • 核心作用:告诉服务端"我想和你建立连接,我的初始序列号是ISN_C,请你确认你能收到我的消息";

  • 客户端状态变化:从CLOSED(关闭) → SYN_SENT(等待服务端确认);

  • 源码关联:客户端socket初始化时,内核会分配ISN,发送SYN包,触发第一次握手。

第二步:服务端确认连接(SYN+ACK包,双向确认第一步)

服务端收到客户端的SYN包后,处于LISTEN状态的服务端会触发accept()的底层逻辑,发送SYN+ACK包,核心细节:

  • 标志位:SYN=1,ACK=1(ACK=1表示确认收到客户端的SYN包,SYN=1表示服务端也发起同步,告诉客户端自己的初始序列号);

  • 序列号:seq=ISN_S(服务端随机生成的初始序列号,比如2000,与客户端ISN无关);

  • 确认号:ack=ISN_C + 1(表示"我已经收到你seq=ISN_C的SYN包,下次请你发送seq=ISN_C+1的数据");

  • 核心作用:告诉客户端"我已经收到你的连接请求,我的初始序列号是ISN_S,我能正常接收你的消息,也请你确认能收到我的消息";

  • 服务端状态变化:从LISTEN(监听) → SYN_RCVD(等待客户端最终确认);

  • 关键:服务端此时会创建一个"半连接队列"(SYN队列),存储未完成三次握手的连接,避免连接丢失。

第三步:客户端最终确认(ACK包,连接建立完成)

客户端收到服务端的SYN+ACK包后,确认服务端的发送和接收能力正常,发送ACK包,核心细节:

  • 标志位:ACK=1(SYN=0,无需再发起同步,仅确认服务端的SYN包);

  • 序列号:seq=ISN_C + 1(基于客户端初始序列号递增,因为第一步发送了1个字节的SYN包,seq需+1);

  • 确认号:ack=ISN_S + 1(表示"我已经收到你seq=ISN_S的SYN+ACK包,下次请你发送seq=ISN_S+1的数据");

  • 核心作用:告诉服务端"我已经收到你的确认消息,我也能正常接收你的消息,连接可以正式建立,我们可以开始传输数据了";

  • 状态变化:客户端从SYN_SENT → ESTABLISHED(连接建立,可传输数据);服务端收到ACK包后,从SYN_RCVD → ESTABLISHED;

  • 源码关联:服务端accept()方法此时返回,客户端和服务端的socket进入可读写状态,后续可通过getInputStream()/getOutputStream()传输数据。

2.3 关键问题解析(面试高频,加分项)

问题1:为什么三次握手不能改成两次?(核心考点)

核心原因:无法确认客户端的接收能力,会导致连接不可靠,存在"失效连接请求"的风险。

假设改成两次握手:客户端发送SYN包,服务端发送SYN+ACK包后,直接认为连接建立,开始发送数据。但如果客户端的SYN包因为网络延迟,迟迟未到达服务端,客户端会重新发送SYN包,建立正常连接;后续延迟的SYN包到达服务端,服务端会误以为是新的连接请求,发送SYN+ACK包后直接建立连接,发送数据,但此时客户端已经没有对应的连接请求,会忽略服务端的数据,导致服务端数据丢失、资源浪费。

三次握手的第三次ACK,就是让客户端确认"服务端能正常接收我的消息",避免上述问题,确保双方的发送和接收能力都正常。

问题2:三次握手过程中,会出现哪些异常?(实战考点)

  • SYN洪水攻击:攻击者伪造大量客户端SYN包,发送给服务端,服务端收到后会进入SYN_RCVD状态,创建半连接队列,当队列满后,服务端无法处理正常的连接请求,导致服务不可用。解决方案:开启SYN Cookie、增大半连接队列、限制SYN包发送频率。

  • SYN包超时重传:客户端发送SYN包后,若未收到服务端的SYN+ACK包,会触发超时重传(默认重传3次,每次超时时间翻倍),若仍未收到,连接建立失败。

问题3:初始序列号(ISN)为什么要随机生成?

避免"序列号重复"导致的数据错乱。如果ISN固定(比如固定为0),当客户端和服务端的连接断开后,若残留的数据包(延迟包)到达服务端,服务端会误以为是新连接的数据包,导致数据错误;随机生成ISN,可大幅降低序列号重复的概率,保证连接的可靠性。


三、TCP四次挥手:释放可靠连接

四次挥手的核心目的:双方确认彼此都没有数据要发送,安全释放连接,避免数据丢失。为什么是四次,不是三次?核心原因是TCP是全双工通信,双方都需要独立关闭自己的"发送通道",每一方的关闭都需要一次FIN(发起关闭)和一次ACK(确认关闭),因此需要四次交互。

前提:双方处于ESTABLISHED状态,假设客户端先主动发起关闭连接(实际双方均可主动发起)。

3.1 四次挥手完整时序图

  1. 客户端 → 服务端:FIN=1,ACK=1,seq=X(客户端当前序列号),ack=Y(服务端已发送的最大序列号+1)

    1. 服务端 → 客户端:ACK=1,seq=Y,ack=X + 1(确认客户端的FIN包)

    2. 服务端 → 客户端:FIN=1,ACK=1,seq=Z(服务端当前序列号),ack=X + 1

    3. 客户端 → 服务端:ACK=1,seq=X + 1,ack=Z + 1(确认服务端的FIN包)

3.2 逐步骤拆解

每一步的核心是"关闭发送通道",注意:FIN包仅表示"我这边没有数据要发送了",但仍能接收对方的数据,直到对方也发送FIN包,才彻底关闭连接。

第一步:客户端发起关闭(FIN包,关闭客户端发送通道)

客户端调用socket.close()时,底层会发送FIN包,核心细节:

  • 标志位:FIN=1,ACK=1(ACK=1表示确认之前收到的服务端数据,FIN=1表示发起关闭);

  • 序列号:seq=X(客户端当前的序列号,即客户端已发送的最后一个字节的序列号+1);

  • 确认号:ack=Y(Y = 服务端已发送的最大序列号 + 1,表示客户端已收到服务端所有数据);

  • 核心作用:告诉服务端"我这边已经没有数据要发送了,我关闭我的发送通道,但我还能接收你的数据,请你处理完剩余数据后,也关闭你的发送通道";

  • 客户端状态变化:从ESTABLISHED → FIN_WAIT_1(等待服务端确认FIN包);

  • 源码关联:close()方法会触发内核发送FIN包,此时客户端的发送通道关闭,无法再向服务端发送数据,但可接收服务端数据。

第二步:服务端确认关闭(ACK包,确认客户端发送通道关闭)

服务端收到客户端的FIN包后,会立即发送ACK包,核心细节:

  • 标志位:ACK=1(FIN=0,此时服务端可能还有数据要发送,暂不关闭自己的发送通道);

  • 序列号:seq=Y(服务端当前的序列号,即服务端已发送的最后一个字节的序列号+1);

  • 确认号:ack=X + 1(表示"我已经收到你关闭发送通道的请求,下次请你不要发送数据了");

  • 核心作用:告诉客户端"我已经收到你关闭发送通道的消息,我会尽快处理完剩余数据,然后关闭我的发送通道";

  • 状态变化:服务端从ESTABLISHED → CLOSE_WAIT(等待自身数据处理完成,准备关闭发送通道);客户端收到ACK包后,从FIN_WAIT_1 → FIN_WAIT_2(等待服务端发起关闭);

  • 关键:CLOSE_WAIT状态是服务端的"隐患点",若服务端处理完数据后,未及时调用close()发送FIN包,会一直处于CLOSE_WAIT状态,导致文件句柄泄露,服务器资源耗尽。

第三步:服务端发起关闭(FIN包,关闭服务端发送通道)

服务端处理完所有剩余数据后,调用serverSocket.close(),发送FIN包,核心细节:

  • 标志位:FIN=1,ACK=1(ACK=1确认客户端的FIN包,FIN=1表示服务端也关闭自己的发送通道);

  • 序列号:seq=Z(服务端处理完数据后的当前序列号,即服务端最后一个发送字节的序列号+1);

  • 确认号:ack=X + 1(再次确认客户端的FIN包,与第二步的ack一致);

  • 核心作用:告诉客户端"我这边也没有数据要发送了,我关闭我的发送通道,现在双方都不能发送数据了,请你确认后,彻底关闭连接";

  • 状态变化:服务端从CLOSE_WAIT → LAST_ACK(等待客户端确认FIN包);

  • 源码关联:服务端close()方法触发内核发送FIN包,此时服务端的发送通道关闭,无法再向客户端发送数据,仅能接收客户端的确认消息。

第四步:客户端最终确认(ACK包,连接彻底释放)

客户端收到服务端的FIN包后,确认服务端的发送通道已关闭,发送ACK包,核心细节:

  • 标志位:ACK=1(FIN=0,无需再发起关闭,仅确认服务端的FIN包);

  • 序列号:seq=X + 1(基于第一步的seq递增,因为第一步发送了1个字节的FIN包);

  • 确认号:ack=Z + 1(表示"我已经收到你关闭发送通道的请求,双方都已关闭发送通道,连接可以彻底释放");

  • 核心作用:告诉服务端"我已经收到你的关闭请求,连接可以彻底释放了";

  • 状态变化:客户端从FIN_WAIT_2 → TIME_WAIT(关键状态,后续详解);服务端收到ACK包后,从LAST_ACK → CLOSED(服务端连接彻底释放);

  • 关键:客户端发送ACK包后,不会立即进入CLOSED状态,而是进入TIME_WAIT状态,等待2MSL(最长报文段寿命,默认1分钟左右),之后才会进入CLOSED状态。

3.3 关键问题解析

问题1:为什么四次挥手不能改成三次?(核心考点)

核心原因:TCP是全双工通信,双方的发送通道需要独立关闭

服务端收到客户端的FIN包后,可能还有未处理完的数据,无法立即发送FIN包(关闭自己的发送通道),只能先发送ACK包,确认客户端的关闭请求;等服务端处理完所有数据后,再发送FIN包,关闭自己的发送通道,因此需要四次交互。如果改成三次,服务端将ACK和FIN包合并发送,可能导致服务端未处理完的数据丢失,破坏可靠传输。

问题2:TIME_WAIT状态的作用是什么?(面试必问)

TIME_WAIT状态是客户端发送最后一个ACK包后,等待2MSL才进入CLOSED状态,核心作用有2个:

  1. 确保服务端能收到最后一个ACK包:如果客户端发送的ACK包丢失,服务端会重传FIN包,客户端在TIME_WAIT状态下能收到重传的FIN包,重新发送ACK包,避免服务端一直处于LAST_ACK状态;

  2. 避免延迟的数据包干扰新连接:连接释放后,网络中可能残留双方发送的延迟数据包,TIME_WAIT状态会等待这些延迟数据包过期(2MSL是数据包在网络中能存活的最长时间),避免这些数据包被新的、相同端口的连接接收,导致数据错乱。

问题3:TIME_WAIT状态过长,会导致什么问题?如何解决?(实战考点)

  • 问题:每个TIME_WAIT状态会占用一个端口,若服务器频繁建立和关闭连接,会出现大量TIME_WAIT状态的连接,导致端口耗尽,无法建立新连接;

  • 解决方案(Linux系统):

    • 调整TIME_WAIT超时时间:net.ipv4.tcp_fin_timeout = 30(单位:秒,默认60秒);

    • 开启端口复用:net.ipv4.tcp_tw_reuse = 1(允许TIME_WAIT状态的端口被重新使用);

    • 开启快速回收:net.ipv4.tcp_tw_recycle = 1(谨慎使用,可能导致NAT网络下的连接异常)。

问题4:服务端大量出现CLOSE_WAIT状态,原因是什么?如何解决?

  • 原因:服务端收到客户端的FIN包后,发送了ACK包,进入CLOSE_WAIT状态,但服务端未及时调用close()方法,导致发送通道未关闭,一直处于CLOSE_WAIT状态;

  • 解决方案:

    • 检查服务端代码,确保处理完数据后,及时调用close()关闭socket;

    • 使用try-with-resources自动关闭socket(Java),避免手动关闭遗漏;

    • 监控服务端CLOSE_WAIT状态,及时排查异常线程(未释放socket的线程)。

问题5:挥手过程中,FIN包会重传吗?

会。无论是客户端还是服务端发送FIN包后,若未收到对方的ACK包,会触发超时重传(默认重传3次),直到收到ACK包,或重传次数耗尽,强制关闭连接。


四、三次握手与四次挥手核心对比(表格速记,面试快速应答)

对比维度 TCP三次握手(建立连接) TCP四次挥手(释放连接)
核心目的 确认双方发送/接收能力,同步ISN,建立连接 确认双方无数据发送,安全释放连接,避免数据丢失
核心标志位 SYN、ACK(SYN用于同步,ACK用于确认) FIN、ACK(FIN用于发起关闭,ACK用于确认关闭)
交互次数 3次(客户端→服务端→客户端) 4次(客户端→服务端→服务端→客户端)
关键状态 SYN_SENT、SYN_RCVD、ESTABLISHED FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、LAST_ACK、TIME_WAIT
核心差异 一次SYN同步,两次ACK确认,无需关闭通道 双方独立关闭发送通道,各需一次FIN+ACK,存在TIME_WAIT
源码关联 new Socket()、accept()触发 socket.close()触发

五、面试高频考点汇总(95分必背,直接套用)

必背考点(直接应对面试提问)

  1. TCP三次握手的步骤、标志位、seq/ack变化,以及每一步的作用;

  2. 三次握手为什么不能改成两次?(核心:确认客户端接收能力,避免失效连接);

  3. TCP四次挥手的步骤、标志位、seq/ack变化,以及每一步的作用;

  4. 四次挥手为什么不能改成三次?(核心:全双工通信,双方需独立关闭发送通道);

  5. TIME_WAIT状态的作用、时长(2MSL)、问题及解决方案;

  6. CLOSE_WAIT状态的成因及解决方案;

  7. SYN洪水攻击的成因及防御方案;

  8. ISN为什么要随机生成?(避免序列号重复,防止数据错乱)。

六、实战注意事项

  1. socket关闭规范:Java中使用try-with-resources自动关闭socket,避免手动关闭遗漏,导致CLOSE_WAIT或TIME_WAIT异常;

  2. 服务器端口优化:生产环境中,调整Linux系统的TIME_WAIT相关参数,避免端口耗尽;

  3. 异常处理:捕获socket连接异常(如ConnectionRefusedException),避免因握手失败导致程序崩溃;

  4. 连接复用:高并发场景下,使用连接池(如HttpClient连接池),减少频繁建立/关闭连接,降低TIME_WAIT压力。


七、总结

TCP三次握手和四次挥手,是TCP面向连接、可靠传输、全双工通信特性的核心体现:

  • 三次握手:同步ISN,确认双方收发能力,三步交互,缺一不可,避免失效连接和数据丢失;

  • 四次挥手:独立关闭双方发送通道,四步交互,核心是全双工特性导致的"分两次关闭",TIME_WAIT状态是可靠释放的关键;

  • 核心记忆点:握手看SYN+ACK,挥手看FIN+ACK,面试重点关注"为什么是三次/四次""TIME_WAIT/CLOSE_WAIT问题",结合源码和实战场景理解,才能真正吃透。

相关推荐
m0_738120722 小时前
应急响应——知攻善防挖矿事件应急溯源详细过程
网络·数据库·安全·web安全
西装没钱买2 小时前
QT组播的建立和使用(绑定特定的网卡,绑定特定IP)
网络·c++·qt·udp·udp组播
小江的记录本3 小时前
【HTTP】HTTP请求方法与状态码(全体系知识总结+附表格)
前端·网络·后端·网络协议·http·状态模式·web
是宇写的啊3 小时前
网络原理1
网络
幸福指北3 小时前
我用 Tauri + Vue 3 + Rust 开发了一款跨平台网络连接监控工具Portview,性能炸裂!
前端·网络·vue.js·tcp/ip·rust
萝卜白菜。3 小时前
http头键名大小写问题
网络·网络协议·http
爱凤的小光4 小时前
Wireshark长时间循环抓包操作说明
网络·测试工具·wireshark
努力的lpp4 小时前
墨者学院登录密码重置漏洞分析溯源wp
网络·网络安全·ctf
haixingtianxinghai4 小时前
Https的加密机制
网络协议·http·https