在传输数据时,网络中会出现的问题
丢包 - 问题的根源
核心思想:TCP通过"确认"机制来感知丢包。
假设:
发送方要发送3个数据包:P1, P2, P3。
情况A:正常传输(无丢包)
发送方 接收方|-------- P1 (seq=1) ------->| 发送P1,启动P1的定时器|<------- ACK for 2 ---------| 收到P1,回复ACK:"下一个我期望seq=2"|-------- P2 (seq=2) ------->| P1确认收到,停止其定时器;发送P2,启动P2定时器|<------- ACK for 3 ---------| 收到P2,回复ACK:"下一个我期望seq=3"|-------- P3 (seq=3) ------->| P2确认收到;发送P3...|<------- ACK for 4 ---------| ...如此继续
(注:ACK号是"期望的下一个序列号",所以ACK for 2 表示seq=1的数据已收到)
情况B:发生丢包(数据包丢失)
发送方 接收方|-------- P1 (seq=1) ------->| |<------- ACK for 2 ---------| P1成功送达|-------- P2 (seq=2) ------->| ** P2在网络中丢失 **|-------- P3 (seq=3) ------->| 发送方不知道P2丢了,继续发P3|<------- ACK for 2 ---------| 接收方发现收到了seq=3,但没收到seq=2!它重复发送"ACK for 2",提醒发送方:"我还在等seq=2!"| (等待... 定时器超时...) ||-------- P2 (seq=2) ------->| ** 超时重传 ** P2|<------- ACK for 4 ---------| 一旦收到P2,接收方发现1,2,3都齐了,直接回复ACK for 4
情况C:发生丢包(ACK确认包丢失)
发送方 接收方|-------- P1 (seq=1) ------->|| (ACK for 2 丢失) X P1的ACK丢了!|-------- P2 (seq=2) ------->| 发送方不知道ACK丢了,继续发P2|<------- ACK for 3 ---------| 接收方回复:"我收到了1和2,期望下一个是3"
关键点:ACK是累积确认的。当发送方收到ACK for 3时,它就知道不仅P2被成功接收,P1也一定被成功接收了(因为接收方是按顺序确认的),尽管它没收到P1的ACK。因此,P1的定时器此时会被停止,P1不会被重传。
超时重传 - 解决丢包的核心武器
超时重传的核心挑战在于:超时时间设为多久?
- RTT与RTO
-
RTT:一个数据包从发送到收到其ACK的往返时间。
-
RTO:超时重传时间。TCP的RTO是根据网络状况动态计算的,而不是一个固定值。
-
工作原理(简化版)
-
持续测量:TCP对每个成功传输的包(非重传的)测量其RTT。
-
平滑计算:使用一个公式来平滑RTT的波动,计算出SRTT。
SRTT = (α * SRTT) + ((1 - α) * RTT_sample)
(α是一个平滑因子,如0.875) -
设置RTO:RTO = SRTT + 4 * DevRTT (DevRTT是RTT的偏差,代表波动程度)
这个机制使得RTO能自适应网络:网络快时RTO变小,反应灵敏;网络拥堵延迟大时,RTO变大,避免不必要的重传。
包重复 - 重传机制的副作用
原因:几乎总是由"不必要的"超时重传引起。
发送方 接收方|-------- P1 (seq=1) ------->| P1正常发送(P1的ACK因网络延迟未到达)|--[Timeout]-- P1 (seq=1) -->| ** 重传P1 **|<------- ACK for 2 ---------| 收到第一个P1或重传的P1,回复ACK|-------- P2 (seq=2) ------->||... (延迟的原始P1到达) ...>| ** 重复的P1到达!**
接收方收到了两个seq=1的包。
TCP的解决方案:序列号
接收方维护着一个RCV.NXT变量,表示期望收到的下一个序列号。
-
当收到seq=1的包时,它处理这个包,并将RCV.NXT更新为2。
-
当又一个seq=1的包到达时,接收方发现seq=1 < RCV.NXT=2,立刻知道这是一个旧的、重复的包,于是直接丢弃。
但这里有个关键细节:接收方在丢弃重复包后,必须再次发送一个ACK for 2。为什么?
因为发送方可能没有收到第一次的ACK for 2(这正是它重传的原因)。这次重复的ACK确保了发送方最终能知道P1已成功接收,从而停止重传定时器并前进。
乱序 - 网络路径不一致的后果
原因:数据包通过网络的不同路径,导致后发先至。
发送方 接收方|-------- P1 (seq=1) ------->||-------- P2 (seq=2) ------->| ** P2走了一条慢路径 **|-------- P3 (seq=3) ------->| |<------- ACK for 2 ---------| 收到P1,期望P2|<------- ACK for 2 ---------| 收到P3!但P2还没到。这是乱序。接收方仍然回复"ACK for 2",催促P2。|-------- P4 (seq=4) ------->| 发送方继续发送,但被接收方缓冲...|<------- ACK for 2 ---------| 再次收到重复ACK|-------- P2 (seq=2) ------->| ** 延迟的P2终于到达 **|<------- ACK for 5 ---------| 接收方现在一下子收到了P2, P3, P4!它一次性确认所有数据,回复"ACK for 5"。
TCP的解决方案:序列号 + 接收缓冲区 + 累积确认
-
接收缓冲区:当接收方收到P3 (seq=3)时,它发现seq=3不是期望的seq=2。但它不会丢弃P3,而是将其存入接收缓冲区。
-
继续发送重复ACK:它仍然回复ACK for 2,告诉发送方:“我还在等seq=2,别的数据我先帮你存着”。
-
填补空缺:当缺失的P2 (seq=2)最终到达时,接收方发现序列号2, 3, 4在缓冲区中已经形成一个连续的数据块。
-
累积确认:接收方此时不会为P2、P3、P4单独发送ACK,而是一次性发送一个ACK for 5,告诉发送方:“5之前的所有字节我都收到了”。这大大减少了ACK的数量,提高了效率。
快速重传:基于乱序的优化
乱序触发的"重复ACK"被TCP作为一个网络可能发生丢包的早期信号。
快速重传算法:当发送方连续收到3个重复的ACK(即总共4个相同的ACK)时,它强烈地暗示这个数据包很可能已经丢失(而不是仅仅延迟)。此时,发送方不再等待超时,而是立即重传被认为丢失的数据包。
发送方 接收方|-------- P1 (seq=1) ------->||<------- ACK for 2 ---------||-------- P2 (seq=2) ------->| ** 丢失 **|-------- P3 (seq=3) ------->||<------- ACK for 2 ---------| Dup ACK #1 (收到P3,但缺P2)|-------- P4 (seq=4) ------->||<------- ACK for 2 ---------| Dup ACK #2 (收到P4,但缺P2)|-------- P5 (seq=5) ------->||<------- ACK for 2 ---------| Dup ACK #3 (收到P5,但缺P2)|*** 快速重传!不等待超时 ***||-------- P2 (seq=2) ------->| ** 立即重传P2 **|<------- ACK for 6 ---------| 收到P2,P3,P4,P5都连续了,累积确认到6
小结
问题 | 触发条件 | TCP的解决机制 | 关键数据结构/概念 |
---|---|---|---|
丢包 | ACK超时或收到3个重复ACK | 超时重传 / 快速重传 | 重传定时器、RTO、重复ACK计数 |
超时重传 | 数据包或ACK丢失 | 自适应RTO算法 | 平滑RTT、RTT偏差 |
包重复 | 不必要的重传 | 序列号 + 丢弃 + 再确认 | 序列号空间、接收窗口下沿 |
乱序 | 数据包非顺序到达 | 序列号 + 接收缓冲区 + 累积确认 | 接收缓冲区、滑动窗口、累积ACK |