目录
[TCP 的三次握手与四次挥手详解](#TCP 的三次握手与四次挥手详解)
[1. 三次握手(Three-Way Handshake)](#1. 三次握手(Three-Way Handshake))
[2. 四次挥手(Four-Way Handshake)](#2. 四次挥手(Four-Way Handshake))
[TCP 为什么可靠?](#TCP 为什么可靠?)
[1. 序列号与确认应答(ACK)](#1. 序列号与确认应答(ACK))
[2. 超时重传(Retransmission)](#2. 超时重传(Retransmission))
[3. 滑动窗口(Sliding Window)](#3. 滑动窗口(Sliding Window))
[4. 拥塞控制(Congestion Control)](#4. 拥塞控制(Congestion Control))
[5. 校验和(Checksum)](#5. 校验和(Checksum))
[6. 面向连接](#6. 面向连接)
[1. 窗口大小与拥塞控制的关系](#1. 窗口大小与拥塞控制的关系)
[2. 糊涂窗口综合征(SWS)](#2. 糊涂窗口综合征(SWS))
[3. 窗口探测(Window Probe)](#3. 窗口探测(Window Probe))
TCP 的三次握手与四次挥手详解
1. 三次握手(Three-Way Handshake)
TCP 建立连接时通过三次握手同步初始序列号(ISN)并协商窗口大小,确保双方具备发送和接收能力。流程如下:
-
客户端 → 服务器(SYN 包)
- 客户端发送 SYN 包,携带初始序列号(ISN<sub>c</sub>)和窗口大小,请求建立连接。
- 标志位:
SYN=1
,ACK=0
。
-
服务器 → 客户端(SYN+ACK 包)
- 服务器收到 SYN 后,分配资源并发送 SYN+ACK 包:
- 确认号(ACK)= ISN<sub>c</sub>+1(表示已收到客户端 SYN)。
- 自身初始序列号(ISN<sub>s</sub>)和窗口大小。
- 标志位:
SYN=1
,ACK=1
。
- 服务器收到 SYN 后,分配资源并发送 SYN+ACK 包:
-
客户端 → 服务器(ACK 包)
- 客户端收到 SYN+ACK 后,发送 ACK 包:
- 确认号(ACK)= ISN<sub>s</sub>+1(表示已收到服务器 SYN)。
- 此时双方进入ESTABLISHED状态,开始传输数据。
- 客户端收到 SYN+ACK 后,发送 ACK 包:
关键作用:
- 防止过时的连接请求(如网络延迟导致的重复 SYN)干扰新连接。
- 同步双方初始序列号,为可靠传输奠定基础。
2. 四次挥手(Four-Way Handshake)
TCP 关闭连接时通过四次挥手确保数据完全传输完毕。流程如下:
-
主动关闭方 → 被动关闭方(FIN 包)
- 主动方(如客户端)发送 FIN 包,表示 "我已无数据要发送",但仍可接收数据。
- 标志位:
FIN=1
,ACK=1
。
-
被动关闭方 → 主动关闭方(ACK 包)
- 被动方(如服务器)确认 FIN,发送 ACK 包:
- 确认号(ACK)= 收到的 FIN 序列号 + 1。
- 此时主动方进入FIN_WAIT_2 状态,被动方进入CLOSE_WAIT状态。
- 被动方(如服务器)确认 FIN,发送 ACK 包:
-
被动关闭方 → 主动关闭方(FIN 包)
- 被动方数据发送完毕后,发送 FIN 包,表示 "我也无数据要发送"。
- 标志位:
FIN=1
,ACK=1
。
-
主动关闭方 → 被动关闭方(ACK 包)
- 主动方确认 FIN,发送 ACK 包:
- 确认号(ACK)= 收到的 FIN 序列号 + 1。
- 主动方进入TIME_WAIT状态(持续 2MSL,防止 ACK 丢失导致被动方重发 FIN),最终双方关闭连接(确保至少留有一次ACK丢失的容错)。
- 主动方确认 FIN,发送 ACK 包:
关键作用:
- 确保双方数据均已完全发送和接收。
- 优雅释放网络资源,避免端口占用。
TCP 为什么可靠?
TCP 通过以下机制实现可靠传输:
1. 序列号与确认应答(ACK)
- 序列号(Sequence Number) :
每个 TCP 段携带数据的首字节序列号,接收方通过序列号按序重组数据。 - 确认应答(ACK) :
接收方返回的 ACK 号表示 "期望接收的下一个字节序列号",确保数据不丢失。
例如:若发送方发送序列号 100-200 的数据包,接收方返回 ACK=201,表示已收到 100-200 并期待 201。
2. 超时重传(Retransmission)
- 发送方为每个数据包设置定时器,若超时未收到 ACK,则重发该数据包。
- 快速重传:若接收方连续收到 3 个相同 ACK(如 ACK=201),表明中间段(如 201-300)丢失,发送方立即重传,无需等待超时。
3. 滑动窗口(Sliding Window)
- 动态调整发送窗口和接收窗口大小,实现流量控制:
- 发送窗口 = min (接收方通告窗口,拥塞窗口)。
- 接收方通过 ACK 中的窗口字段告知发送方自身缓冲区剩余空间,防止过载。
4. 拥塞控制(Congestion Control)
- 防止网络过载,通过调整拥塞窗口(cwnd)实现:
- 慢启动:初始 cwnd=1MSS,每收到一个 ACK 则 cwnd+1,指数增长至阈值(ssthresh)。
- 拥塞避免:超过阈值后,每轮 RTT 仅 cwnd+1,线性增长。
- 拥塞发生时:将 ssthresh 降至当前 cwnd 的一半,cwnd 重置为 1MSS,重新进入慢启动。
5. 校验和(Checksum)
- 每个 TCP 段包含校验和字段,接收方通过校验和验证数据完整性,若错误则丢弃并等待重传。
6. 面向连接
- 通过三次握手建立连接,四次挥手关闭连接,确保双方状态同步,避免数据丢失。
总结对比
特性 | 三次握手 | 四次挥手 | TCP 可靠性机制 |
---|---|---|---|
目的 | 建立连接,同步 ISN 和窗口大小 | 关闭连接,确保数据完全传输 | 保证数据准确、有序、无丢失 |
核心动作 | SYN → SYN+ACK → ACK | FIN → ACK → FIN → ACK | 序列号、ACK、重传、窗口、校验和 |
状态转换 | CLOSED → SYN_SENT → ESTABLISHED | ESTABLISHED → FIN_WAIT_1 → ... → CLOSED | 持续维护发送 / 接收状态 |
可靠性保障 | 防止过时连接干扰 | 确保资源优雅释放 | 端到端的数据传输质量保证 |
TCP滑动窗口
TCP 滑动窗口的初始值是在三次握手过程中协商确定的,之后在数据传输阶段会根据网络状况和接收方缓冲区状态动态调整。具体机制如下:
三次握手期间的窗口协商
初始窗口大小的交换
- 客户端 → 服务器(SYN 包) :
客户端在 SYN 包中通过Window Size
字段(16 位)告知服务器自己初始接收窗口的大小 (单位:字节)。例如,若客户端发送Window = 65535
,表示客户端初始能接收 65535 字节的数据。 - 服务器 → 客户端(SYN+ACK 包) :
服务器在响应的 SYN+ACK 包中,同样通过Window Size
字段告知客户端自己的初始接收窗口大小。 - 窗口扩大因子(Window Scaling) :
若双方支持Window Scaling
选项(通过 TCP 选项协商),实际窗口大小 = 窗口字段值 × 2^ 缩放因子(例如,缩放因子为 2,则窗口大小 = 65535×4=262140 字节)。

数据传输阶段的窗口动态调整
窗口滑动机制
- 发送方窗口 :
发送方维护两个指针:SND.UNA
(已发送但未确认的最早字节)和SND.NXT
(下一个要发送的字节),窗口大小 = 接收方通告的窗口值(通过 ACK 包携带)。 - 接收方窗口 :
接收方通过 ACK 包中的Window
字段告知发送方自己当前的可用缓冲区大小。例如:- 若接收方处理数据缓慢,缓冲区接近满,则发送
Window = 0
,触发发送方的窗口探测机制。 - 若接收方处理完一批数据,缓冲区空闲,则增大窗口值(如
Window = 131072
)。
- 若接收方处理数据缓慢,缓冲区接近满,则发送

关键细节补充
1. 窗口大小与拥塞控制的关系
- 实际发送窗口 = min (接收方通告窗口,拥塞窗口) :
接收方窗口由接收方缓冲区决定,拥塞窗口由发送方根据网络拥塞情况动态调整(如慢启动、拥塞避免算法)。
2. 糊涂窗口综合征(SWS)
- 接收方优化 :
若接收方缓冲区仅释放少量空间(如 512 字节),可能暂不发送窗口更新,而是等待缓冲区释放更多空间(如≥MSS)后再通知发送方。 - 发送方优化 :
若发送方有少量数据(如 100 字节)要发送,可能等待积累到 MSS(如 1460 字节)后再发送,避免产生小数据包。
3. 窗口探测(Window Probe)
- 当接收方窗口为 0 时,发送方会定期发送窗口探测包(仅含 1 字节数据),迫使接收方返回最新窗口大小,防止连接饿死。
TCP和UDP有什么区别
核心设计目标
TCP | UDP |
---|---|
面向连接:通过三次握手建立连接,四次挥手关闭连接,确保通信双方状态同步。 | 无连接:无需建立连接,直接发送数据,资源开销小。 |
可靠传输:确保数据准确、有序到达,通过序列号、ACK、重传等机制实现。 | 不可靠传输:不保证数据到达顺序或完整性,可能丢包、乱序。 |
流量控制:通过滑动窗口动态调整发送速率,避免接收方过载。 | 无流量控制:发送方按固定速率发送,接收方可能因来不及处理而丢包。 |
拥塞控制:根据网络拥塞状态调整发送窗口,避免网络崩溃。 | 无拥塞控制:可能导致网络拥塞(如 UDP 洪水攻击)。 |
协议格式对比
字段 | TCP 头部(20 字节基础) | UDP 头部(8 字节) |
---|---|---|
源端口 | 16 位 | 16 位 |
目的端口 | 16 位 | 16 位 |
序列号 | 32 位(用于有序重组数据) | 无 |
确认号 | 32 位(用于 ACK 应答) | 无 |
窗口大小 | 16 位(用于流量控制) | 无 |
校验和 | 16 位(强制) | 16 位(可选,IPv4 中可忽略) |
标志位 | SYN、ACK、FIN、RST、PSH、URG 等 | 无 |
TCP报文结构

UDP报文结构

指标 | TCP | UDP |
---|---|---|
传输效率 | 低(头部开销大,需维护连接状态) | 高(头部仅 8 字节,无连接开销) |
实时性 | 低(重传、排序等引入延迟) | 高(无重传,延迟小) |
吞吐量 | 稳定(拥塞控制避免网络崩溃) | 不稳定(可能因网络拥塞丢包) |
资源占用 | 高(需维护连接状态表) | 低(无连接状态) |
典型应用场景
场景 | TCP 适用 | UDP 适用 |
---|---|---|
Web 浏览(HTTP) | ✅ 需可靠传输,避免页面错乱 | ❌ 不可靠传输可能导致页面缺失 |
文件传输(FTP) | ✅ 确保文件完整无误 | ❌ 丢包会导致文件损坏 |
电子邮件(SMTP) | ✅ 邮件内容必须完整送达 | ❌ 不可靠传输会导致邮件丢失 |
实时音视频 | ❌ 重传会加剧延迟,影响体验 | ✅ 容忍少量丢包,优先保证实时性 |
DNS 查询 | ❌ 单次查询延迟高(需握手) | ✅ 快速响应,少量丢包可重试 |
游戏联机 | ❌ 角色位置同步需低延迟 | ✅ 允许少量预测误差,优先流畅性 |
可靠性机制对比
机制 | TCP | UDP |
---|---|---|
连接管理 | 三次握手建立连接,四次挥手关闭 | 无连接过程 |
序列号与 ACK | 强制使用,确保数据有序性 | 无 |
超时重传 | 支持(快速重传 + 超时重传) | 无(需应用层自行实现) |
滑动窗口 | 支持流量控制和拥塞控制 | 无 |
错误校验 | 强制校验和,保证数据完整性 | 可选校验和(IPv4 中常忽略) |
总结选择策略
- 选 TCP:当可靠性比性能更重要时(如文件传输、网页浏览)。
- 选 UDP:当实时性比可靠性更重要时(如视频会议、游戏),或应用层自行实现可靠性机制(如 QUIC 协议)。
TCP的粘包和拆包
基本概念
TCP 是面向字节流的协议,其传输的数据无边界 。发送方连续发送的多个数据包(如多次send()
),在接收方可能呈现为:
- 粘包:多个数据包被 "粘" 在一起,接收方一次读取到多个完整或不完整的包。
- 拆包:一个数据包被 "拆分" 成多次接收,接收方需多次读取才能获取完整包。
示例场景 :
发送方依次发送两个数据包:[数据包1]
和 [数据包2]
,接收方可能收到:
- 粘包 :
[数据包1+数据包2]
(一次读取两个包)。 - 拆包 :
[数据包1的前半部分]
和[数据包1的后半部分+数据包2]
(两次读取才完整)。
成因分析
TCP 粘包 / 拆包由以下因素共同导致:
-
TCP 的 Nagle 算法
- 为减少网络中小数据包的数量,Nagle 算法会将短时间内的小数据包合并发送。
- 例如:发送方先后调用
send("HELLO")
和send("WORLD")
,可能被合并为"HELLOWORLD"
发送。
-
TCP 滑动窗口与拥塞控制
- 发送窗口大小动态调整,若窗口较小,一个完整包可能被拆分为多次发送。
- 例如:窗口大小为 1000 字节,而数据包大小为 1500 字节,则会被拆分为两个片段。
-
接收缓冲区管理
- 接收方缓冲区已满时,未完全接收的包会暂存,导致后续数据包与残留数据 "粘" 在一起。
-
网络延迟与 MTU 限制
- 网络层最大传输单元(MTU,通常为 1500 字节)会限制 TCP 数据包的大小,超过 MTU 的包会被 IP 层分片。
如何解决粘包和拆包
- 粘包:这个思路其实很清晰,就是把它拆开呗,具体就是看怎么拆了,比如我们可以固定长度,我们规定每个包都是10个字节,那么就10个字节切一刀,这样拆开解析就ok了。
- 半包:半包其实就是信息还不完整,我们需要等接收到全部的信息之后再作处理,当我们识别这是一个不完整的包时候,我们先hold住,不作处理,等待
数据完整再处理。这里关键点在于,我们如何才能知道此时完整了?上面说的固定长度其实也是一点,当然还有更多更好的解决方案,我们接着往下看。
实际常见解决粘包与半包问题有三个方案:
- 固定长度:
- 每个数据包的总长度固定,不足部分填充特定字符(如
\0
)。 - 接收方按固定长度读取数据,确保每次读取对应一个完整包。
优点 | 缺点 |
---|---|
实现简单 | 浪费带宽(需填充) |
无需维护缓冲区 | 不适合变长数据 |
解析速度快 | 需预估最大长度 |
- 分隔符
- 使用特殊字符(如
\r\n
、\0
)标记数据包的结束。 - 接收方需维护缓冲区,累积数据直到找到分隔符。
优点 | 缺点 |
---|---|
灵活支持变长数据 | 需维护缓冲区 |
适合文本协议(如 HTTP) | 分隔符可能出现在数据中 |
实现较简单 | 性能略低于固定长度 |
- 固定长度字段+内容
- 在数据包头部添加固定长度的字段(如 4 字节整数),指示包的总长度。
- 接收方先读取长度字段,再按长度读取完整数据。
优点 | 缺点 |
---|---|
高效处理任意长度数据 | 实现复杂度较高 |
无需填充,节省带宽 | 需处理多字节序问题 |
解析逻辑清晰 | 需确保长度字段完整接收 |
选择建议:
- 若数据长度固定 → 优先用固定长度协议。
- 若为文本协议且分隔符不会出现在数据中 → 用分隔符协议。
- 若需高效处理任意长度数据 → 用长度前缀协议(现代协议主流方案)