TCP 三次握手与四次挥手
一、TCP 协议的核心特性:为什么需要三次握手与四次挥手?
在深入细节前,我们先明确 TCP 的定位:TCP 是面向连接、可靠传输、基于字节流的传输层协议,与 “无连接、不可靠” 的 UDP 形成鲜明对比。这些特性决定了 TCP 必须通过特定机制实现 “连接管理”:
- 面向连接:通信前必须先建立连接,通信后需断开连接(类似打电话 “拨号 - 通话 - 挂电话”);
- 可靠传输:通过 “确认机制、重传机制、滑动窗口” 等确保数据不丢失、不重复、按序到达;
- 字节流:数据以连续字节的形式传输,而非 UDP 的 “数据包” 形式。
而 “三次握手” 是 TCP 建立可靠连接的核心机制,“四次挥手” 是断开连接的标准流程 —— 二者共同保证了 TCP 通信的 “可靠性” 和 “资源高效利用”。
二、TCP 三次握手:如何建立可靠连接?
“三次握手”(Three-Way Handshake)是客户端与服务器之间通过交换 3 个 TCP 报文段,确认双方 “发送能力” 和 “接收能力” 均正常,最终建立可靠连接的过程。
1. 核心前提:TCP 报文段的关键字段
理解三次握手前,需先掌握 TCP 报文段中 3 个核心控制字段(位于 TCP 头部),它们是握手过程的 “信号旗”:
- SYN(Synchronize):同步序列编号,用于发起连接请求,标识 “我要建立连接,并发送初始序列号”;
- ACK(Acknowledgment):确认编号,用于响应对方请求,标识 “我已收到你的数据,下一次请发送这个编号之后的数据”;
- FIN(Finish):结束标志,用于请求断开连接(四次挥手时使用);
- Seq(Sequence Number):序列号,TCP 将数据按字节编号,Seq 标识当前报文段的第一个字节的编号(确保数据按序传输);
- Ack(Acknowledgment Number):确认号,标识 “期望收到的下一个字节的编号”,即 “已收到的最大序列号 + 1”。
2. 三次握手完整流程
假设场景:客户端(如浏览器)要与服务器(如 Web 服务器)建立 TCP 连接,进而传输 HTTP 请求。
第一步:客户端发送 “连接请求”(SYN 报文)
- 发起方:客户端
- 报文类型:SYN 报文(仅 SYN 标志位为 1)
- 关键字段:
- Seq = x(x 是客户端生成的随机初始序列号,如 100);
- SYN = 1(表示 “我请求建立连接,并同步我的初始序列号 x”);
- 目的:客户端向服务器表明 “我有发送数据的能力,想和你建立连接,请确认你的接收能力”。
此时客户端状态从CLOSED
(关闭)变为SYN-SENT
(已发送 SYN 请求)。
第二步:服务器响应 “连接确认”(SYN+ACK 报文)
- 发起方:服务器
- 报文类型:SYN+ACK 报文(SYN 和 ACK 标志位均为 1)
- 关键字段:
- Seq = y(y 是服务器生成的随机初始序列号,如 200);
- SYN = 1(服务器同步自己的初始序列号 y,同时请求建立连接);
- Ack = x + 1(确认已收到客户端的 Seq=x,下一次期望收到 x+1 及之后的数据);
- ACK = 1(表示 “我已收到你的连接请求,我的接收能力正常,同时我也向你发起连接请求”);
- 目的:服务器向客户端反馈 “我已收到你的请求(接收能力正常),我也有发送能力,想和你建立连接,请确认你的接收能力”。
此时服务器状态从LISTEN
(监听)变为SYN-RCVD
(已收到 SYN 请求,等待客户端确认)。
第三步:客户端发送 “最终确认”(ACK 报文)
- 发起方:客户端
- 报文类型:ACK 报文(仅 ACK 标志位为 1)
- 关键字段:
- Seq = x + 1(客户端的下一个序列号,因第一步已发送 1 个字节的 SYN 报文,Seq 递增 1);
- Ack = y + 1(确认已收到服务器的 Seq=y,下一次期望收到 y+1 及之后的数据);
- ACK = 1(表示 “我已收到你的连接请求,我的接收能力正常,连接可以正式建立”);
- 目的:客户端向服务器确认 “我已收到你的响应(接收能力正常),连接可以正式建立,我们可以开始传输数据了”。
此时客户端状态从SYN-SENT
变为ESTABLISHED
(已建立连接);服务器收到 ACK 报文后,状态也从SYN-RCVD
变为ESTABLISHED
。
至此,TCP 三次握手完成,双方进入 “已连接” 状态,可开始传输应用层数据(如 HTTP 请求、文件数据等)。
3. 为什么是 “三次” 而不是两次或四次?
这是 TCP 三次握手最经典的问题,核心答案是 “确保双方发送能力和接收能力均正常,同时避免‘无效连接请求’浪费资源”:
- 两次握手不可行:若仅两次握手,服务器发送 SYN+ACK 后就认为连接已建立,但客户端可能因网络延迟未收到该报文,不会发送 ACK。此时服务器会一直等待客户端的数据,浪费端口和内存资源(“半连接队列” 溢出风险);
- 四次握手没必要:三次握手已能完整确认双方的 “发送 + 接收” 能力(客户端确认服务器:能发能收;服务器确认客户端:能发能收),第四次握手属于冗余操作,会增加网络延迟。
三、TCP 四次挥手:如何断开连接?
当数据传输完成后,TCP 需要通过 “四次挥手”(Four-Way Wavehand)断开连接,确保双方都已完成数据接收,避免数据丢失。与三次握手不同,四次挥手是 “双向断开”—— 客户端和服务器需分别向对方发起断开请求并确认。
1. 四次挥手完整流程
假设场景:客户端与服务器已完成数据传输(如浏览器已接收完网页数据),需断开 TCP 连接。
第一步:客户端发送 “断开请求”(FIN 报文)
- 发起方:客户端
- 报文类型:FIN 报文(仅 FIN 标志位为 1)
- 关键字段:
- Seq = u(u 是客户端已发送的最后一个字节的序列号 + 1,如客户端已发送到 1000 字节,Seq=1001);
- FIN = 1(表示 “我已完成数据发送,想断开我的发送连接”);
- 目的:客户端向服务器表明 “我这边没有数据要发送了,你可以准备接收我的最后数据,但你仍可以向我发送数据”。
此时客户端状态从ESTABLISHED
变为FIN-WAIT-1
(等待服务器确认断开)。
第二步:服务器响应 “断开确认”(ACK 报文)
- 发起方:服务器
- 报文类型:ACK 报文(仅 ACK 标志位为 1)
- 关键字段:
- Seq = v(v 是服务器已发送的最后一个字节的序列号 + 1,如服务器已发送到 2000 字节,Seq=2001);
- Ack = u + 1(确认已收到客户端的 FIN 报文,下一次期望收到 u+1 及之后的数据,实际客户端已无数据发送);
- ACK = 1(表示 “我已收到你的断开请求,我会继续向你发送剩余数据,请你等待”);
- 目的:服务器向客户端反馈 “我知道你要断开了,但我可能还有数据没发完,请你先别关闭接收通道,等我发完数据再断开”。
此时服务器状态从ESTABLISHED
变为CLOSE-WAIT
(等待自身数据发送完成后,再发起断开请求);客户端收到 ACK 后,状态从FIN-WAIT-1
变为FIN-WAIT-2
(等待服务器的 FIN 报文)。
第三步:服务器发送 “断开请求”(FIN 报文)
- 发起方:服务器
- 报文类型:FIN 报文(仅 FIN 标志位为 1)
- 关键字段:
- Seq = w(w 是服务器已发送的最后一个字节的序列号 + 1,若服务器在第二步后发送了 500 字节数据,Seq=2001+500=2501);
- FIN = 1(表示 “我已完成所有数据发送,想断开我的发送连接”);
- Ack = u + 1(再次确认已收到客户端的 FIN 报文,与第二步的 Ack 一致);
- 目的:服务器向客户端表明 “我这边也没有数据要发送了,我们可以彻底断开连接了”。
此时服务器状态从CLOSE-WAIT
变为LAST-ACK
(等待客户端的最终确认)。
第四步:客户端发送 “最终确认”(ACK 报文)
- 发起方:客户端
- 报文类型:ACK 报文(仅 ACK 标志位为 1)
- 关键字段:
- Seq = u + 1(客户端的下一个序列号,因第一步已发送 1 个字节的 FIN 报文,Seq 递增 1);
- Ack = w + 1(确认已收到服务器的 FIN 报文,下一次期望收到 w+1 及之后的数据,实际服务器已无数据发送);
- ACK = 1(表示 “我已收到你的断开请求,连接可以彻底断开了”);
- 目的:客户端向服务器确认 “我知道你也断开了,连接可以正式关闭”。
此时客户端状态从FIN-WAIT-2
变为TIME-WAIT
(等待 2MSL 时间,确保服务器收到 ACK);服务器收到 ACK 后,状态从LAST-ACK
变为CLOSED
(已关闭连接)。
客户端等待 2MSL(MSL:Maximum Segment Lifetime,报文最大生存时间,默认 2 分钟)后,若未收到服务器的重传 FIN 报文,确认服务器已收到 ACK,状态从TIME-WAIT
变为CLOSED
。至此,TCP 四次挥手完成,连接彻底断开。
2. 为什么是 “四次” 而不是三次?
核心原因是TCP 的 “双向通信” 特性——TCP 连接是 “全双工”(双方可同时发送数据),断开连接时需分别关闭 “客户端→服务器” 和 “服务器→客户端” 两个方向的通道:
- 第一次挥手:客户端关闭 “发送通道”,告诉服务器 “我不发了”;
- 第二次挥手:服务器确认 “收到关闭请求”,但 “发送通道” 仍开(可能还有数据要发);
- 第三次挥手:服务器发完数据后,关闭 “发送通道”,告诉客户端 “我也不发了”;
- 第四次挥手:客户端确认 “收到关闭请求”,关闭 “接收通道”。
若改为三次挥手,服务器需在第二次挥手时同时发送 FIN+ACK(类似三次握手的 SYN+ACK),但此时服务器可能仍有数据未发送,强行关闭会导致数据丢失 —— 因此四次挥手是 “优雅断开” 的必要设计。
3. 四次挥手的关键问题与实战分析
(1)“TIME-WAIT” 状态的作用
客户端在第四次挥手后会进入TIME-WAIT
状态,等待 2MSL 时间,这是 TCP 设计的关键,主要解决两个问题:
- 确保服务器收到最终 ACK:若第四次挥手的 ACK 报文丢失,服务器会重传 FIN 报文,客户端在 TIME-WAIT 期间可收到并重发 ACK,避免服务器因未收到 ACK 而一直处于
LAST-ACK
状态; - 避免 “旧连接” 干扰新连接:2MSL 是报文在网络中最大生存时间,等待 2MSL 可确保网络中所有该连接的旧报文都已过期,避免新连接使用相同端口时,收到旧连接的残留报文。
注意:若服务器频繁出现大量TIME-WAIT
连接(如高并发短连接场景),可能导致端口耗尽。可通过调整 Linux 参数优化(如net.ipv4.tcp_tw_reuse
允许端口复用,net.ipv4.tcp_tw_recycle
快速回收 TIME-WAIT 连接)。
(2)“CLOSE-WAIT” 状态堆积的问题
服务器在第二次挥手后会进入CLOSE-WAIT
状态,若该状态连接大量堆积,通常是应用层代码未正确关闭 TCP 连接(如服务器处理完请求后,未调用close()
函数释放连接)。
排查方案:
- 使用
netstat -an | grep CLOSE-WAIT
查看堆积的连接; - 检查应用层代码,确保请求处理完成后主动关闭连接(如 Java 中的
socket.close()
、Python 中的socket.close()
); - 调整 TCP 超时参数(如
net.ipv4.tcp_keepalive_time
,开启 TCP 保活机制,检测死连接并关闭)。
四、总结
TCP 三次握手与四次挥手是网络通信的 “基石”,其设计充分体现了 TCP 协议的 “可靠性” 和 “严谨性”:
- 三次握手:通过 3 次报文交换,确认双方发送 / 接收能力正常,建立可靠连接,避免无效连接浪费资源;
- 四次挥手:通过 4 次报文交换,双向关闭连接,确保双方数据都已传输完成,避免数据丢失;
- 关键状态:
SYN-SENT
、SYN-RCVD
(握手),FIN-WAIT-1
、CLOSE-WAIT
、TIME-WAIT
(挥手),这些状态是排查网络问题的重要依据。