TCP协议的三次握手与四次挥手深度解析
文章目录
一、前置知识:TCP 报文核心字段(理解握手 / 挥手的基础)
二、三次握手:建立可靠连接(含状态流转 + 报文细节)
1. 前提:连接初始化状态
2. 分步拆解(含状态变迁 + 报文实例)
3. 深度补充:三次握手的关键细节(
三、四次挥手:关闭可靠连接(含双向关闭 + 状态流转)
1. 前提:连接关闭初始化状态
2. 分步拆解(含状态变迁 + 报文实例)
3. 四次挥手的关键细节
四、总结:三次握手与四次挥手的核心对比(含异常场景)
五、实际运维 / 开发中的关联场景
一、前置知识:TCP 报文核心字段(理解握手 / 挥手的基础)
在拆解流程前,先明确 TCP 报文中与 “连接管理” 强相关的字段(这些字段是握手 / 挥手的 “沟通语言”):
字段 | 长度 | 核心作用 |
SYN | 1 位 | 同步位,SYN=1表示 “发起连接同步请求”(仅在握手阶段使用,挥手阶段为 0) |
ACK | 1 位 | 确认位,ACK=1表示 “该报文包含确认号(ack)”(握手 / 挥手阶段均需置 1,除第一次 SYN) |
FIN | 1 位 | 终止位,FIN=1表示 “发起关闭连接请求”(仅在挥手阶段使用,握手阶段为 0) |
seq | 32 位 | 序号字段,标识 “当前报文的数据起始序号”(握手时传初始序号 ISN,数据传输时传数据序号) |
ack | 32 位 | 确认号字段,标识 “期望接收的下一个序号”(值 = 对方已发序号的最大值 + 1,即 “对方该序号及之前的报文已收到”) |
Window | 16 位 | 窗口大小,协商双方的 “接收缓冲区容量”(握手时确定初始窗口,传输时动态调整) |
MSS | 4 字节 | 最大分段大小,协商 “单次 TCP 报文能承载的最大数据量”(握手时确定,避免 IP 分片) |
Checksum | 16 位 | 校验和,检测报文是否在传输中损坏(确保握手 / 挥手报文不被篡改) |
注意点:
- 只有ACK=1时,ack字段才有效;
- SYN=1或FIN=1的报文,即使没有数据,也会 “消耗 1 个序号”(后续数据序号需 + 1);
- 正常数据传输的报文,SYN=0且FIN=0,seq为数据起始序号。
二、三次握手:建立可靠连接
TCP 连接建立是 “客户端主动发起,服务器被动监听” 的过程,核心是双向确认收发能力 + 协商核心参数(ISN、MSS、窗口),以下结合 “状态变迁” 和 “实际报文例子” 拆解。
1. 前提:连接初始化状态
- 客户端:初始状态为CLOSED(无连接),需先向服务器发起连接;
- 服务器:提前进入LISTEN状态(通过bind()+listen()系统调用,监听指定端口,如 80、443)。
2. 分步拆解(含状态变迁 + 报文实例)
假设客户端生成的初始序号ISN_client=100,服务器生成的初始序号ISN_server=200,MSS 协商为 1460 字节(以太网环境默认),窗口大小协商为 8192 字节。
步骤 | 发起方 | 状态变迁 | 报文类型 | 完整报文字段(关键部分) | 交互逻辑与细节 |
1 | 客户端 | CLOSED → SYN_SENT | SYN 报文(第一次握手) | SYN=1,ACK=0(无需确认),seq=100(ISN_client),MSS=1460,Window=8192 | ① 客户端向服务器的目标端口(如 80)发送 “连接请求”,核心是传递自己的 ISN 和协商参数;② 为什么ACK=0?因为此时还未收到服务器任何报文,无确认内容;③ 客户端进入SYN_SENT状态:等待服务器的SYN+ACK响应,若超时(默认重传 3-5 次,间隔指数增长)则连接失败。 |
2 | 服务器 | LISTEN → SYN_RCVD | SYN+ACK 报文(第二次握手) | SYN=1,ACK=1,seq=200(ISN_server),ack=100+1=101,MSS=1460,Window=8192 | ① 服务器收到 SYN 后,先校验(端口是否监听、MSS 是否支持),通过后返回 “同步 + 确认”;② ack=101的含义:“我已收到你序号 100 的 SYN 报文,下次请发 101 及以后的数据”;③ 服务器同时传递自己的 ISN(200),完成双向同步的第一步;④ 服务器进入SYN_RCVD状态:等待客户端的最终 ACK,若超时则释放资源。 |
3 | 客户端 | SYN_SENT → ESTABLISHED | ACK 报文(第三次握手) | SYN=0,ACK=1,seq=100+1=101(SYN 消耗 1 个序号),ack=200+1=201,Window=8192 | ① 客户端收到SYN+ACK后,确认服务器的 ISN 和协商参数,返回最终确认;② seq=101的含义:“我的 SYN 报文(100)已消耗 1 个序号,后续数据从 101 开始”;③ ack=201的含义:“我已收到你序号 200 的 SYN 报文,下次请发 201 及以后的数据”;④ 客户端进入ESTABLISHED状态:连接正式建立,可开始传输数据;⑤ 服务器收到 ACK 后,也进入ESTABLISHED状态,双方开始双向数据传输。 |
3. 深度补充:三次握手的关键细节
- ISN(初始序号)是怎么生成的?为什么不能固定为 1?
ISN 由操作系统动态生成,规则是 “基于系统时间戳(毫秒级)+ 随机偏移量”,确保每个新连接的 ISN 不同;
如果固定为 1,当 “旧连接的延迟报文”(如之前断开的连接中,未被及时丢弃的序号 1-10 的报文)到达新连接时,新连接会误认为是 “当前连接的合法数据”,导致数据错乱(比如覆盖正常数据)。
- 什么是 “SYN 泛洪攻击”?如何防御?
攻击原理:攻击者伪造大量 “源 IP 不存在” 的 SYN 报文,向服务器发送,服务器收到后进入SYN_RCVD状态并等待 ACK,但永远收不到(源 IP 无效),最终服务器资源(端口、内存)被耗尽,无法处理正常连接;
防御方案:① 开启 TCP SYN Cookie(服务器不保存SYN_RCVD状态,用 ISN 和客户端 IP 生成 Cookie,后续 ACK 中验证);② 限制 SYN 报文的接收速率(如 iptables 规则);③ 缩短SYN_RCVD状态的超时时间(默认 30 秒,可改为 5 秒)。
- 三次握手时,MSS 和 Window 字段的作用是什么?
① MSS(最大分段大小):协商 “单次 TCP 报文能携带的最大数据量”(MSS=MTU-IP 头 - TCP 头,以太网 MTU=1500,故 MSS=1500-20-20=1460),避免数据传输时触发 IP 分片(分片会降低效率);
② Window(窗口大小):协商 “双方接收缓冲区的最大容量”(如 8192 字节),后续数据传输时,发送方不能超过对方窗口大小发数据,避免接收方缓冲区溢出(这是 TCP 流量控制的基础)。
三、四次挥手:关闭可靠连接
TCP 连接是 “全双工”(客户端→服务器、服务器→客户端两个方向独立传输),关闭时需 “分别关闭每个方向的连接”,因此需四次交互。以下结合 “状态变迁” 和 “文件传输场景” 拆解。
1. 前提:连接关闭初始化状态
- 双方均处于ESTABLISHED状态,假设客户端已完成数据发送(如浏览器下载完网页),主动发起关闭;
- 服务器可能仍有未发送完的数据(如服务器向客户端发送 “下载完成确认信息”)。
2. 分步拆解(含状态变迁 + 报文实例)
延续三次握手的序号:客户端当前序号seq=105(已发 101-105 共 5 字节数据),服务器当前序号seq=203(已发 201-203 共 3 字节数据)。
步骤 | 发起方 | 状态变迁 | 报文类型 | 完整报文字段(关键部分) | 交互逻辑与细节 |
1 | 客户端 | ESTABLISHED → FIN_WAIT_1 | FIN 报文(第一次挥手) | FIN=1,ACK=1,seq=105(客户端最后发的序号),ack=203+1=204,Window=8192 | ① 客户端向服务器发送 “关闭请求”,核心是 “告知服务器:我这边的发送方向已无数据,准备关闭”;② seq=105的含义:“我已发完 101-105 的数据,FIN 报文占用 105 这个序号”;③ ack=204的含义:“我已收到你 201-203 的数据,下次请发 204 及以后的(若有)”;④ 客户端进入FIN_WAIT_1状态:等待服务器对 FIN 的 ACK 响应。 |
2 | 服务器 | ESTABLISHED → CLOSE_WAIT | ACK 报文(第二次挥手) | FIN=0,ACK=1,seq=203(服务器最后发的序号),ack=105+1=106,Window=8192 | ① 服务器收到 FIN 后,先返回 ACK 确认 “已知道客户端要关闭发送方向”;② ack=106的含义:“我已收到你序号 105 的 FIN 报文,你这边的发送方向可以关闭了”;③ 服务器进入CLOSE_WAIT状态:此时服务器的 “接收方向已关闭”(不能再收客户端数据),但 “发送方向仍打开”(可继续向客户端发未完成的数据,如确认信息);④ 关键:CLOSE_WAIT状态的时长由服务器应用层决定 —— 若应用层没及时调用close(),服务器会一直停在这个状态(运维中 “CLOSE_WAIT 堆积” 就是这个原因)。 |
3 | 服务器 | CLOSE_WAIT → LAST_ACK | FIN 报文(第三次挥手) | FIN=1,ACK=1,seq=203+1=204(服务器发完剩余数据后的序号),ack=106,Window=8192 | ① 服务器发完所有未完成的数据(如 “下载完成确认”)后,向客户端发送 “关闭请求”,告知 “我这边的发送方向也无数据了”;② seq=204的含义:“我已发完 201-203 的数据,现在发 FIN 报文,序号用 204”;③ 服务器进入LAST_ACK状态:等待客户端对 FIN 的 ACK 响应,若超时则重发 FIN(默认重传 3 次)。 |
4 | 客户端 | FIN_WAIT_2 → TIME_WAIT → CLOSED | ACK 报文(第四次挥手) | FIN=0,ACK=1,seq=105+1=106(客户端 FIN 消耗 1 个序号),ack=204+1=205,Window=8192 | ① 客户端收到服务器的 FIN 后,返回 ACK 确认 “已知道服务器要关闭发送方向”;② ack=205的含义:“我已收到你序号 204 的 FIN 报文,你这边的发送方向可以关闭了”;③ 客户端不立即关闭,而是进入TIME_WAIT状态(等待 2MSL),再进入CLOSED;④ 服务器收到 ACK 后,立即进入CLOSED状态,释放所有资源。 |
3. 四次挥手的关键细节
- 为什么服务器需要 “CLOSE_WAIT” 状态?什么情况下会出现 “CLOSE_WAIT 堆积”?
① CLOSE_WAIT是服务器 “处理剩余数据” 的缓冲状态 —— 服务器收到客户端 FIN 后,可能还有数据没发完(如数据库查询结果、文件收尾信息),需先传完数据再发自己的 FIN,因此需要CLOSE_WAIT过渡;
② “CLOSE_WAIT 堆积” 的原因:服务器应用层代码存在 bug,未调用close()函数释放连接(如处理完数据后忘记关闭 socket),导致服务器一直停在CLOSE_WAIT状态,端口和内存被耗尽(解决:检查应用层代码,确保数据发送完后调用close(),或设置 socket 超时自动关闭)。
- TIME_WAIT 状态为什么要等 “2MSL”?MSL 的默认值是多少?
① MSL(Maximum Segment Lifetime):报文在网络中能存活的最长时间(默认 30 秒,不同操作系统可能调整),2MSL 即 60 秒;
② 等待 2MSL 的两个核心目的:
- 防止最后一个 ACK 丢失:若服务器没收到客户端的 ACK,会在 1MSL 内重发 FIN,客户端在 2MSL 内还能收到并重发 ACK,避免服务器一直停在LAST_ACK;
- 清空网络残留报文:2MSL 内,本次连接的所有报文(包括延迟的 FIN、ACK)会从网络中消失,新连接(用相同的源 IP、源端口、目的 IP、目的端口)不会收到旧报文干扰;
③ 特殊场景:若服务器需要快速重启(如运维更新),可通过net.ipv4.tcp_tw_reuse(复用 TIME_WAIT 状态的端口)或net.ipv4.tcp_tw_recycle(快速回收 TIME_WAIT 连接)优化,但需谨慎(可能导致报文错乱)。
- “半关闭” 状态具体指什么?实际场景中如何体现?
半关闭是 “仅关闭一个方向的连接,另一个方向仍可传输数据” 的状态,对应步骤 1-2 后的状态:
例如:客户端(浏览器)向服务器下载 100MB 文件,下载完成后客户端发 FIN(步骤 1),服务器回 ACK(步骤 2):
- 客户端的 “发送方向” 关闭(不能再向服务器发数据),但 “接收方向” 仍打开(能收服务器的 “下载完成确认”);
- 服务器的 “接收方向” 关闭(不能再收客户端数据),但 “发送方向” 仍打开(可向客户端发 “确认信息”);
直到服务器发完确认信息并送 FIN(步骤 3),客户端确认后(步骤 4),才进入 “全关闭”。
总结:三次握手与四次挥手的核心对比
维度 | 三次握手(建立连接) | 四次挥手(关闭连接) |
核心目标 | 双向确认收发能力,协商 ISN、MSS、Window | 双向关闭连接,确保双方数据均传输完成 |
状态流转 | 客户端:CLOSED→SYN_SENT→ESTABLISHED服务器:CLOSED→LISTEN→SYN_RCVD→ESTABLISHED | 客户端:ESTABLISHED→FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT→CLOSED服务器:ESTABLISHED→CLOSE_WAIT→LAST_ACK→CLOSED |
关键报文 | SYN → SYN+ACK → ACK | FIN → ACK → FIN → ACK |
序号消耗 | SYN 报文消耗 1 个序号,ACK 不消耗 | FIN 报文消耗 1 个序号,ACK 不消耗 |
异常场景 1 | SYN 丢失:客户端重传 SYN(指数退避) | FIN 丢失:服务器重传 FIN(默认 3 次) |
异常场景 2 | SYN 泛洪:服务器资源耗尽,无法处理正常连接 | CLOSE_WAIT 堆积:服务器应用层未调用 close () |
实际举例 | 浏览器访问网站时,与服务器建立 TCP 连接 | 下载完成后,浏览器与服务器关闭 TCP 连接 |
五、实际运维 / 开发中的关联场景
- 查看 TCP 连接状态(Linux):
用netstat -an | grep :80或ss -tuln查看端口连接状态,若发现大量SYN_RCVD,可能是 SYN 泛洪;若大量CLOSE_WAIT,需检查应用层代码。
- TCP 连接超时配置:
开发中需设置合理的 “连接超时”(如三次握手超时 3 秒)和 “读写超时”(如数据传输超时 30 秒),避免连接长期占用资源。
- HTTP 与 TCP 的关系:
一次 HTTP 请求(如打开网页)对应 “一次 TCP 三次握手→传输 HTTP 数据→一次 TCP 四次挥手”(HTTP/1.1 默认长连接,会复用 TCP 连接,减少握手 / 挥手开销)。
总的来说,用一句话简短的总结就是;TCP 三次握手通过 “SYN→SYN+ACK→ACK” 确认双方收发能力、协商初始参数以建立可靠连接,四次挥手通过 “FIN→ACK→FIN→ACK” 分方向确认数据传输完成以关闭全双工连接。好了,今天就到这里,今天依旧是深蹲不写BUG,希望这些东西对大家有所帮助。