5.1 UDP 协议
UDP (User Datagram Protocol)
无连接、不可靠、面向报文的传输层协议。报文不编号、不建立连接、不拥塞控制,首部仅 8 字节,开销小、实时性好。UDP 报文首部:源端口(2B) + 目的端口(2B) + 长度(2B) + 校验和(2B)。
| 对比项 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 面向连接 |
| 可靠性 | 不可靠,尽最大努力 | 可靠,确认/重传/排序 |
| 首部开销 | 8 字节 | 20 字节(最小) |
| 拥塞/流量控制 | 无 | 有 |
| 通信方式 | 支持单播/多播/广播 | 仅点对点 |
| 应用 | DNS、DHCP、视频流 | HTTP、FTP、SMTP |
5.2 TCP 报文段
TCP 面向连接、可靠传输、全双工、点对点。TCP 报文段首部 20~60 字节,重要字段:
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口/目的端口 | 各 2B | 标识应用进程 |
| 序号 seq | 4B | 本报文数据首字节的序号 |
| 确认号 ack | 4B | 期望收到对方下一字节的序号 |
| 数据偏移 | 4 位 | 首部长度(以 4B 为单位) |
| 控制位 | 6 位 | URG/ACK/PSH/RST/SYN/FIN |
| 窗口 rwnd | 2B | 接收端通告的可用缓存(流量控制) |
| 校验和 | 2B | 检验首部+数据 |
| 紧急指针 | 2B | URG=1 时有效 |
5.3 TCP 连接管理 — 三次握手
TCP 建立连接需三次握手,确保双方收发能力正常。
为何三次?两次握手无法防止已失效的连接请求到达服务端导致资源浪费;三次握手能让双方都确认对方收发正常,且同步初始序号 (ISN)。
5.4 TCP 连接释放 — 四次挥手
TCP 释放连接需四次挥手,因 TCP 全双工,每方向需单独关闭。
TIME_WAIT 与 2MSL:主动关闭方进入 TIME_WAIT 后需等待 2MSL(最大报文段寿命),原因:①保证最后 ACK 能到达对端(若丢失可重传);②防止本次连接的报文残留干扰新连接。
5.5 可靠传输、流量控制与滑动窗口
TCP 通过序号+确认+重传实现可靠传输。采用累计确认机制,确认号表示此前所有字节均已正确接收。
流量控制
接收方通过 ACK 报文中的窗口字段 rwnd 通告自身可用缓存,发送方据此调整发送窗口,防止接收方被淹没。流量控制 vs 拥塞控制:流量控制是端到端的,抑制发送方以适应接收方;拥塞控制是全局的,抑制发送方以适应网络。
TCP 可靠传输的滑动窗口机制与数据链路层类似(见第 3 章),但基于字节流且窗口动态变化。
5.6 TCP 拥塞控制
拥塞控制通过调节拥塞窗口 cwnd 控制发送速率。发送窗口 = min(rwnd, cwnd)。四种算法:
| 算法 | 规则 |
|---|---|
| 慢开始 | cwnd 从 1 起按 2 的倍数指数增长(每 RTT 翻倍),到 ssthresh 改用拥塞避免 |
| 拥塞避免 | cwnd 每经过一个 RTT 线性加 1(加性增) |
| 快重传 | 收到 3 个重复 ACK 立即重传丢失报文,不等超时 |
| 快恢复 | 3 重复 ACK 时:ssthresh = cwnd/2,cwnd = ssthresh,进入拥塞避免(非从 1 开始) |
超时 vs 3 重复 ACK:超时认为拥塞严重 → cwnd=1 重新慢开始;3 重复 ACK 认为拥塞较轻 → cwnd 减半进入快恢复。
5.7 TCP 校验和计算
TCP 校验和用于检测报文在传输过程中是否出现差错,其计算范围覆盖首部 + 数据 + 伪首部三部分。
TCP 校验和计算范围
TCP 校验和对整个报文段进行计算,包括:① TCP 首部(20~60 字节);② TCP 数据部分;③ 伪首部(12 字节,仅参与校验和计算,不实际传输)。
伪首部的组成(12 字节):
| 字段 | 长度 | 说明 |
|---|---|---|
| 源 IP 地址 | 4 字节 | 发送方 IP 地址 |
| 目的 IP 地址 | 4 字节 | 接收方 IP 地址 |
| 协议号 | 2 字节 | TCP = 6(UDP = 17) |
| TCP 长度 | 2 字节 | TCP 首部 + 数据的总长度(不含伪首部) |
伪首部的作用:伪首部将网络层 IP 地址与传输层数据关联起来,确保报文确实到达了正确的目的地。若目的 IP 地址不匹配,校验和将不通过。
校验和计算步骤:
| 步骤 | 操作 |
|---|---|
| 1 | 将伪首部、TCP 首部和数据拼接成一个整体(若数据长度为奇数,末尾补 1 字节 0) |
| 2 | 将整个数据块按 16 位(2 字节)分组 |
| 3 | 对所有 16 位分组进行二进制反码求和(按反码相加,若有进位则回卷到最低位) |
| 4 | 对结果取反(1 → 0,0 → 1),即得到校验和 |
| 5 | 发送方将校验和填入 TCP 首部的校验和字段;接收方收到后重新计算,若结果为 0 则校验通过 |
5.8 TCP 超时重传机制
往返时间 RTT (Round-Trip Time)
从发送方发送报文段到收到接收方确认所经历的时间。由于网络负载和路由变化,RTT 是动态波动的。
平滑 RTT(SRTT):TCP 不直接使用一次测量的 RTT,而是对多次测量进行加权平均,以获得更稳定的 RTT 估计值:
| 公式 | 说明 |
|---|---|
| SRTT = α × SRTT + (1-α) × RTT_sample | α 通常取 0.875(RFC 6298),SRTT 为平滑后的 RTT,RTT_sample 为新测量值 |
| RTTVAR = β × RTTVAR + (1-β) × |RTT_sample - SRTT| | RTTVAR 为 RTT 偏差的平滑估计,β 通常取 0.25 |
超时重传时间 RTO(Retransmission Timeout):
RTO 计算公式(RFC 6298):RTO = SRTT + max(G, K × RTTVAR),其中 G 为网络固有延迟(最小 1 秒),K 通常取 4。首次测量时 RTO 初始值取 3 秒。
Karn 算法
当报文段被重传时,发送方无法区分确认是针对原始报文还是重传报文。Karn 算法规定:不对重传报文的 RTT 进行测量,仅对首次传输的报文测量 RTT,避免 RTT 估计失真。若确实需要在重传时测量,可使用时间戳选项(RFC 7323)。
快速重传与快速恢复的触发条件:
| 触发条件 | TCP 反应 |
|---|---|
| 超时计时器到期 | 拥塞严重 → cwnd = 1,ssthresh = cwnd/2,重新进入慢开始 |
| 收到 3 个重复 ACK(同一确认号) | 拥塞较轻 → 触发快重传(立即重传,不等超时)+ 快恢复 |
5.9 TCP 拥塞控制详解
以下给出慢开始、拥塞避免、快重传与快恢复算法的详细伪代码与流程。
算法:慢开始(Slow Start)
cwnd = 1 // 拥塞窗口初始化为 1 个 MSS
ssthresh = 64 // 慢开始阈值,默认 64KB
function slow_start():
// 每收到一个新的 ACK(累计确认推进)
// cwnd 按指数增长,每 RTT 翻倍
for each ACK that acknowledges new data:
if cwnd < ssthresh:
cwnd = cwnd + 1 // 指数增长阶段
else:
// 达到阈值,切换到拥塞避免
congestion_avoidance()
// 发送窗口 = min(rwnd, cwnd)
// 当 cwnd >= ssthresh 时退出慢开始
算法:拥塞避免(Congestion Avoidance)
function congestion_avoidance():
// 每 RTT 线性加 1(加性增)
for each ACK that acknowledges new data:
// cwnd 每收到一个 ACK 增加 1/cwnd
// 经过一个完整 RTT 后,cwnd 增加约 1
cwnd = cwnd + 1 / cwnd // 精确实现:每个 ACK 加 1/cwnd
if cwnd >= ssthresh:
// 保持在拥塞避免阶段
pass
// 检测到拥塞时(超时或 3 重复 ACK):
// ssthresh = max(cwnd/2, 2)
// 超时:cwnd = 1,重新慢开始
// 3 重复 ACK:cwnd = ssthresh,快恢复
算法:快重传与快恢复(Fast Retransmit & Fast Recovery)
duplicate_ack_count = 0
function on_duplicate_ack(ack_number):
duplicate_ack_count = duplicate_ack_count + 1
// 触发快重传:收到 3 个重复 ACK
if duplicate_ack_count == 3:
ssthresh = max(cwnd / 2, 2) // 阈值减半
cwnd = ssthresh // 窗口减半
// 立即重传丢失的报文段
retransmit(ack_number)
enter_fast_recovery()
function enter_fast_recovery():
// 快恢复阶段:每收到一个重复 ACK,cwnd 加 1
// (因为重复 ACK 说明部分数据仍在流动中)
for each duplicate ACK after the 3rd:
cwnd = cwnd + 1
// 继续重传下一个未确认的报文
// 当收到新的 ACK(确认了丢失数据):
// cwnd = ssthresh
// 回到拥塞避免阶段
function on_timeout():
// 超时:拥塞严重
ssthresh = max(cwnd / 2, 2)
cwnd = 1
duplicate_ack_count = 0
enter_slow_start()
快恢复 vs 慢开始:3 个重复 ACK 后,快恢复将 cwnd 减半而非重置为 1,因为重复 ACK 表明网络仍有一定承载能力,拥塞相对较轻。超时则表明网络拥塞严重,必须从 1 开始重新慢开始。
拥塞控制状态转换图: