当前位置: 首页 > news >正文

深入理解 TCP 协议:三次握手与四次挥手的底层原理

一、TCP 协议基础:为什么需要 “握手” 与 “挥手”?

在正式讲解 TCP 连接管理流程前,我们需要先深入理解 TCP 协议的两个核心特性 —— 面向连接(Connection-oriented)和可靠性(Reliability),这也是 "三次握手" 与 "四次挥手" 机制存在的根本原因:

  1. 面向连接特性详解

    • 建立连接:TCP 在通信前必须通过三次握手建立逻辑连接,确保通信双方都准备好进行数据传输。这类似于打电话时的拨号接通过程。
    • 关闭连接:通信结束后需要通过四次挥手正常关闭连接,释放系统资源(如端口号、缓冲区等)。如果不规范关闭可能导致"僵尸连接"占用资源。
    • 连接状态管理:TCP 维护精确的连接状态机(LISTEN、SYN-SENT、ESTABLISHED等),确保连接生命周期的规范管理。
  2. 可靠性保障机制

    • 确认机制(ACK):接收方对每个收到的数据包发送确认应答
    • 序列号机制(Sequence Number):为每个字节编号,解决数据包乱序问题
    • 超时重传:未收到ACK时会重传数据包
    • 流量控制:通过滑动窗口机制匹配收发双方的速率
    • 拥塞控制:通过慢启动、拥塞避免等算法防止网络过载

三次握手的核心作用

  • 同步双方的初始序列号(ISN),ISN是基于时钟的动态随机值,防止历史连接干扰
  • 交换TCP参数(如MSS最大报文段大小、窗口缩放因子等)
  • 确认双方的收发能力正常
  • 为后续可靠传输建立基础,序列号将用于确认和重传机制

四次挥手的必要性

  • 全双工特性决定需要分别关闭两个方向的数据流
  • FIN报文需要得到确认,确保数据完整传输
  • TIME_WAIT状态等待2MSL(最大报文生存时间),防止最后ACK丢失
  • 彻底释放连接资源,避免出现半关闭状态(Half-close)

示例场景: 当客户端访问web服务器时:

  1. 三次握手:客户端发送SYN→服务器回复SYN+ACK→客户端回复ACK
  2. 数据传输:客户端发送HTTP请求,服务器返回网页数据
  3. 四次挥手:任一方先发送FIN→收到ACK→另一方向发送FIN→最终ACK确认

二、三次握手:TCP 连接的建立过程

1. 三次握手概述

三次握手是 TCP/IP 协议中客户端与服务器之间通过交换 3 个 TCP 报文段来完成连接建立的关键过程。我们以 "客户端发起 HTTP 请求" 这一常见场景为例,详细拆解每一步的操作细节和底层原理。

2. 核心概念铺垫

TCP 报文关键字段

字段含义详细说明
SYN同步标志位用于发起连接请求,当取值为 1 时表示"请求同步序列号"。在三次握手中,第一个SYN报文由客户端发出,第二个SYN报文由服务器返回
ACK确认标志位用于确认收到对方的报文,取值为 1 时表示"确认有效"。在三次握手中,第二个和第三个报文都带有ACK标志
Seq序列号32位无符号数,用于标识当前发送的报文段中第一个数据字节的编号。每个方向(客户端到服务器或服务器到客户端)都有独立的序列号空间
Ack确认号32位无符号数,表示期望收到对方下一个报文段的序列号,即"已收到的最大序列号 + 1"。确认号用于确保数据按序到达

3. 三次握手具体流程

假设场景:客户端(IP:192.168.1.100,端口:54321)向服务器(IP:10.0.0.1,端口:80)发起 TCP 连接,建立后用于传输 HTTP 请求。

第一步:客户端发送 "连接请求"(SYN 报文)

  • 发送方:客户端
  • 报文类型:SYN 报文(设置SYN=1,ACK=0)
  • 关键字段
    • Seq = x(x 为客户端随机生成的初始序列号,例如 1000,用于标识客户端后续发送数据的起始编号)
    • 目的端口:80(HTTP服务标准端口)
  • 目的:告诉服务器"我想和你建立连接,请你确认,并同步你的序列号给我"
  • 服务器行为
    1. 收到 SYN 报文后,确认客户端的连接请求有效
    2. 在内存中创建传输控制块(TCB),记录客户端的初始序列号 x
    3. 准备向客户端发送"确认 + 同步"报文
    4. 将该连接状态置为SYN_RECEIVED(已收到SYN)

第二步:服务器回复"确认 + 同步"(SYN+ACK 报文)

  • 发送方:服务器
  • 报文类型:SYN+ACK 报文(设置SYN=1,ACK=1)
  • 关键字段
    • Seq = y(y 为服务器随机生成的初始序列号,例如 2000,用于标识服务器后续发送数据的起始编号)
    • Ack = x + 1(确认客户端的 Seq=x 已收到,期望下次收到客户端的 Seq=x+1)
  • 目的:告诉客户端"我已收到你的连接请求(确认),同时我也把我的序列号同步给你(同步),请你确认我的序列号"
  • 客户端行为
    1. 收到 SYN+ACK 报文后,确认服务器已响应连接请求,且同步了服务器的序列号 y
    2. 验证 Ack 字段是否为 x+1(若不符,说明报文丢失或错误,会重传 SYN 报文)
    3. 准备向服务器发送"最终确认"报文
    4. 将本地连接状态置为ESTABLISHED(已建立)

第三步:客户端发送"最终确认"(ACK 报文)

  • 发送方:客户端
  • 报文类型:ACK 报文(设置SYN=0,ACK=1)
  • 关键字段
    • Seq = x + 1(基于第一步的 Seq=x,后续发送数据将从 x+1 开始)
    • Ack = y + 1(确认服务器的 Seq=y 已收到,期望下次收到服务器的 Seq=y+1)
  • 目的:告诉服务器"我已收到你的序列号,连接可以正式建立了"
  • 服务器行为
    1. 收到 ACK 报文后,验证 Ack 字段是否为 y+1(若不符,会重传 SYN+ACK 报文)
    2. 验证通过后,TCP 连接正式建立(双方进入 ESTABLISHED 状态)
    3. 此时客户端可以立即发送应用层数据(如 HTTP 请求),服务器则可以开始接收数据

4. 为什么必须是"三次"握手?

这是网络协议面试中最常见的问题之一,核心原因是避免"历史重复的连接请求"导致服务器资源浪费:

问题场景分析

假设采用"两次握手":

  1. 客户端发送第一个SYN报文(Seq=1000)后,因网络拥塞,该报文长时间滞留
  2. 客户端超时未收到响应,重新发送SYN报文(Seq=2000),这次成功与服务器建立连接并完成通信后关闭
  3. 此时滞留在网络中的旧SYN报文(Seq=1000)终于到达服务器
  4. 服务器误以为是新的连接请求,回复SYN+ACK(Seq=3000, Ack=1001)
  5. 在两次握手机制下,服务器认为连接已建立,开始等待客户端发送数据

产生的问题

  • 客户端已经关闭了该连接,不会回复服务器的SYN+ACK
  • 服务器会一直维护这个半开的连接,占用端口、内存等资源
  • 如果大量这样的无效连接积累,最终会导致服务器资源耗尽

三次握手如何解决

  • 在三次握手中,服务器必须收到客户端的第三次ACK才会真正建立连接
  • 对于滞留的旧SYN报文,服务器会回复SYN+ACK
  • 但客户端已经关闭该连接上下文,不会发送对应的第三次ACK
  • 服务器在超时(通常是30秒到2分钟)后会自动关闭这个半开的连接
  • 这样确保了服务器不会长期维护无效连接,避免了资源浪费

其他考虑因素

  1. 序列号同步:三次握手确保了双方都确认了对方的初始序列号,为可靠传输奠定基础
  2. 防止DoS攻击:不完全的连接请求不会占用服务器完整资源
  3. 网络适应性:适应不可靠的网络环境,确保连接建立的可靠性
  4. 对称性设计:双方都确认了对方的接收能力,建立了双向通信通道

三、四次挥手:TCP 连接的关闭过程

1. 四次挥手的必要性

TCP 连接的关闭需要四次交互(俗称"四次挥手"),这是由 TCP 协议的全双工通信特性决定的。在 HTTP 1.1 中,当浏览器与服务器完成页面数据传输后,就会启动这个关闭流程。让我们通过一个具体的网页访问场景来说明:

假设用户访问 www.example.com,浏览器(客户端)与服务器建立 TCP 连接并完成数据传输后,浏览器会主动发起关闭连接请求。

2. 四次挥手的具体流程

第一步:客户端发送FIN报文(FIN=1)

  • 发送方:客户端(如浏览器)

  • 报文详情

    • 控制位:FIN=1, ACK=1
    • 序列号:Seq = m(假设客户端总共发送了100字节数据,序列号从x开始,则m = x + 100)
    • 确认号:Ack = n(确认已收到服务器的所有数据)
  • 状态变化

    • 客户端:ESTABLISHED → FIN_WAIT_1
    • 服务器:保持ESTABLISHED
  • 实际应用场景:当浏览器完成HTTP请求并收到完整响应后,就会发送这个FIN报文,表示"我的数据都发完了"。

第二步:服务器回复ACK报文

  • 发送方:服务器

  • 报文详情

    • 控制位:ACK=1
    • 序列号:Seq = n
    • 确认号:Ack = m + 1
  • 状态变化

    • 客户端:FIN_WAIT_1 → FIN_WAIT_2
    • 服务器:ESTABLISHED → CLOSE_WAIT
  • 关键点:此时服务器可能还有数据要发送给客户端,比如:

    • 未完成的HTTP分块传输
    • 服务器推送的数据
    • 延迟确认的数据包

第三步:服务器发送FIN报文

  • 发送方:服务器

  • 报文详情

    • 控制位:FIN=1, ACK=1
    • 序列号:Seq = p(如果在CLOSE_WAIT期间又发送了50字节数据,则p = n + 50)
    • 确认号:Ack = m + 1
  • 状态变化

    • 服务器:CLOSE_WAIT → LAST_ACK
    • 客户端:保持FIN_WAIT_2
  • 典型场景:Web服务器完成所有响应数据的发送后,才会发送这个FIN报文。

第四步:客户端发送最终ACK

  • 发送方:客户端

  • 报文详情

    • 控制位:ACK=1
    • 序列号:Seq = m + 1
    • 确认号:Ack = p + 1
  • 状态变化

    • 客户端:FIN_WAIT_2 → TIME_WAIT(持续2MSL,通常1-4分钟)
    • 服务器:收到ACK后立即进入CLOSED状态

3. 关键问题深度解析

3.1 为什么不能是三次挥手?

核心原因:TCP的全双工特性要求两个方向的数据通道需要独立关闭。

  • 示例场景:假设采用三次挥手:

    1. 客户端FIN
    2. 服务器FIN+ACK
    3. 客户端ACK

    这样会导致如果服务器在第二步之后还有数据要发送,这些数据就会丢失。四次挥手确保了:

    • 先确认关闭客户端→服务器方向
    • 再处理服务器→客户端方向的数据
    • 最后确认关闭反向通道

3.2 TIME_WAIT状态的重要性

等待2MSL(Maximum Segment Lifetime)的两个关键原因

  1. 可靠终止

    • 如果最后一个ACK丢失,服务器会重传FIN
    • 客户端在TIME_WAIT期间能收到这个重传的FIN并再次发送ACK
    • 典型值:Linux默认MSL为60秒,所以TIME_WAIT为120秒
  2. 防止旧连接数据混淆

    • 确保网络中所有属于这个连接的报文都消失
    • 避免新建立的相同四元组(源IP、源端口、目标IP、目标端口)连接收到旧数据

实际影响

  • 在高性能服务器上,大量TIME_WAIT连接会占用端口资源
  • 解决方案:
    • 启用SO_REUSEADDR套接字选项
    • 调整TCP参数(如net.ipv4.tcp_tw_reuse)

3.3 CLOSE_WAIT状态堆积问题

产生原因

  • 服务器应用层没有正确调用close()关闭套接字
  • 常见于以下情况:
    • 代码存在资源泄漏
    • 异常处理路径没有关闭连接
    • 使用连接池但管理不当

检测与解决

# Linux查看CLOSE_WAIT连接
netstat -antp | grep CLOSE_WAIT

解决方案

  1. 代码层面:
    • 确保所有执行路径都关闭套接字
    • 使用try-finally或RAII模式管理资源
  2. 系统层面:
    • 调整TCP keepalive参数
    • 设置合理的连接超时

4. 实际应用中的变体

4.1 同时关闭

当双方同时发起关闭时,流程会简化为:

  1. 客户端FIN → FIN_WAIT_1
  2. 服务器FIN → FIN_WAIT_1
  3. 双方收到FIN后都发送ACK → 直接进入CLOSING状态
  4. 收到ACK后进入TIME_WAIT

4.2 半关闭状态

使用shutdown()函数可以实现半关闭:

  • SHUT_WR:关闭写入方向,类似发送FIN
  • SHUT_RD:关闭读取方向(较少使用)
  • 典型应用:HTTP的流水线传输

5. 性能优化建议

  1. 服务器端

    • 适当调整TIME_WAIT超时
    • 启用TCP快速回收
    echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
    

  2. 客户端

    • 使用连接池复用连接
    • 避免频繁创建短连接
  3. 监控指标

    • TIME_WAIT连接数
    • CLOSE_WAIT连接数
    • 连接建立/关闭频率

通过深入理解TCP四次挥手机制,开发人员可以更好地诊断网络问题,优化应用程序的网络性能,构建更可靠的分布式系统。

四、实战:通过 Wireshark 抓包验证三次握手与四次挥手

1. 实验环境准备

客户端配置

  • 操作系统:Windows 10/11 或 macOS 10.15 及以上版本
  • 浏览器:Chrome/Firefox/Edge 最新版本
  • 推荐配置:8GB 内存,100MB 以上硬盘空间

服务器选择(二选一)

本地服务器方案

  • 安装 Nginx 1.18+ 版本
  • 配置监听端口为 80(默认配置)
  • 本地访问地址:http://localhosthttp://127.0.0.1

公网服务器方案

  • 选择稳定网站如 www.baidu.com
  • 确保能正常访问 HTTP 协议(非 HTTPS)

必备工具

  • Wireshark 3.6+ 版本
  • 安装时勾选所有组件(特别是 WinPcap/Npcap)
  • 管理员权限运行(Windows 需要)

2. 详细抓包步骤

网卡配置

  1. 以管理员身份启动 Wireshark
  2. 在首页选择当前活动的网卡:
    • 有线连接:通常显示为 "Ethernet" 或 "本地连接"
    • 无线连接:显示为 "Wi-Fi" 或 "Wireless"
  3. 右键网卡选择 "Start Capture"

过滤规则设置

  1. 在过滤栏输入精确表达式:tcp.port == 80
  2. 可选辅助过滤:
    • 本地测试:ip.addr == 127.0.0.1
    • 公网测试:ip.addr == <目标服务器IP>

操作流程

  1. 清空现有抓包数据(Edit → Clear All)
  2. 点击绿色鲨鱼图标开始抓包
  3. 立即打开浏览器访问目标地址
  4. 等待页面完全加载(包括所有资源)
  5. 关闭浏览器所有标签页
  6. 返回 Wireshark 点击红色方块停止抓包

3. 协议分析详解

三次握手分析

  1. 筛选 tcp.flags.syn==1 and tcp.flags.ack==0 找到初始 SYN 包
    • 查看 Seq=0(实际为随机值)
    • 确认源端口为临时端口(通常 > 32768)
  2. 筛选 tcp.flags.syn==1 and tcp.flags.ack==1 找到 SYN-ACK 响应
    • 确认 Ack=Seq+1
    • 目标端口应为 80
  3. 筛选 tcp.flags.ack==1 找到最后的 ACK
    • 确认 Seq=初始 Ack 值
    • Ack=服务端 Seq+1

数据传输观察

  1. 查找 HTTP GET 请求(筛选 http.request
  2. 观察后续 TCP 段的 Seq/Ack 编号变化规律
  3. 注意窗口大小(Window Size)的动态调整

四次挥手分析

  1. 首次 FIN 包特征:
    • 筛选 tcp.flags.fin==1
    • 通常由客户端发起(浏览器关闭时)
    • 状态转为 FIN_WAIT_1
  2. 服务端 ACK 响应:
    • 确认 Ack=Seq+1
    • 客户端进入 FIN_WAIT_2
  3. 服务端 FIN 包:
    • 可能包含最后的数据(PSH 标志)
    • 服务端进入 LAST_ACK
  4. 最终 ACK:
    • 客户端进入 TIME_WAIT(约4分钟)
    • 观察序列号最终确认

异常情况排查

  1. 重传检测:tcp.analysis.retransmission
  2. 乱序检查:tcp.analysis.out_of_order
  3. 连接重置:tcp.flags.reset==1

4. 实验报告要点

建议记录以下关键数据:

  1. 三次握手各阶段的精确时间戳(右键包 → "Set Time Reference")
  2. 初始序列号(原始值而非相对值)
  3. 四次挥手各标志位的组合情况
  4. 计算整个会话的持续时间(Statistics → Conversations → TCP)
  5. 流量统计(Statistics → HTTP → Packet Counter)

http://www.dtcms.com/a/395259.html

相关文章:

  • PyTorch 神经网络工具箱
  • 机器学习-多因子线性回归
  • 国产化Excel开发组件Spire.XLS教程:Python 写入 Excel 文件,数据写入自动化实用指南
  • 08 - spring security基于jdbc的账号密码
  • 解决SSL证书导致源站IP被泄露的问题
  • Worst Western Hotel: 1靶场渗透
  • 电子电气架构 --- 软件开发与产品系统集成流程(上)
  • 运维安全08,日志检测和 tcpdump (抓包) 的介绍以及使用
  • DSC 归档配置相关
  • 彭博社-BloombergGPT金融大模型
  • GPT5 Codex简单快速上手
  • Linux配置白名单限制访问_ipset+iptables
  • 多元化通证经济模型:DAO的神经和血液
  • 高系分十六:web应用
  • 【LeetCode热题100(27/100)】合并两个有序链表
  • 嵌入式(SOC+FreeRTOS)汽车仪表盘接口参数安全:规范遵循与防护实践
  • Maven 完整教程
  • 数据驱动下的用户画像系统:从0到1的技术实战与避坑指南
  • 同一个灰色,POI取出来却是白色:一次Excel颜色解析的踩坑记录
  • Excel——常用函数一
  • 立项依据不足会给项目带来哪些风险
  • 从 0 到 1 精通 SkyWalking:分布式系统的 “透视镜“ 技术全解析
  • SkyWalking 核心概念与智能探针工作原理深度揭秘(下)
  • Dockerfile入门指南
  • iOS 原生开发全流程解析,iOS 应用开发步骤、Xcode 开发环境配置、ipa 文件打包上传与 App Store 上架实战经验
  • 数据分析报告的写作流程
  • 当你的断点在说谎:深入解析RTOS中的“幽灵”Bug
  • [BUG]MarkupSafe==3.0.2
  • 机器学习笔试选择题:题组1
  • 79-数据可视化-地图可视化