①TCP 靠什么保证可靠
网络层 IP 是不可靠的:会丢包、会乱序、会重复、会出错。TCP 在它之上用一套组合拳,把不可靠的信道变成了可靠、有序、不丢、不重的字节流。
① 序列号 + 确认应答
每个字节编号,接收方用 ACK 告诉发送方“我收到哪了”,没被确认的就重发。
② 超时重传
发出后启动计时器,超时未收到 ACK 就重传;RTO 根据 RTT 动态计算。
③ 快速重传
不等超时,收到 3 个重复 ACK 就立刻重传丢失报文。
④ 校验和
检测报文在传输中是否损坏,损坏则丢弃(等同于丢包,触发重传)。
⑤ 排序与去重
接收端按序列号重排乱序到达的报文,并丢弃重复报文。
⑥ 流量控制
用滑动窗口匹配收发双方速度,防止接收方被冲垮。
⑦ 拥塞控制
感知网络状况,防止把网络挤爆(详见拥塞控制专篇)。
注意区分:流量控制保护的是接收方(怕它处理不过来),拥塞控制保护的是网络(怕链路堵死)。两者都通过限制发送窗口实现,但出发点完全不同。
②序列号与确认号:可靠性的地基
序列号(Sequence Number)
TCP 给每一个字节编号,报文头的 seq 是本报文第一个字节的编号。比如发送 1000 字节、起始 seq=1,则下一个报文 seq=1001。
连接建立时的初始序列号(ISN)是随机的,不是从 0 开始——这是为了防止旧连接的迷途报文被误认为新连接的数据,也是安全考虑(防止序列号预测攻击)。
确认号(Acknowledgment Number)
确认号的含义,一句话背下来:ack = N 表示“N 之前的所有字节我都收到了,我接下来期待第 N 个字节”。
注意:它表示的是期望收到的下一个字节序号,而不是“我收到的最后一个字节”。这个细节面试经常考。
累积确认(Cumulative ACK):TCP 的 ack 是累积的——ack=1001 意味着 1001 之前全部都收到了。这也带来一个问题:如果中间某个报文丢了(比如 501~1000 丢了,但 1001~1500 收到了),接收方只能反复回 ack=501,无法告诉发送方“后面的我其实收到了”——这正是 SACK 要解决的问题。
③超时重传:RTO 是怎么算出来的
发送方每发一个报文就启动一个重传计时器,超时(RTO, Retransmission Timeout)还没收到 ACK 就重传。
难点:RTO 设多少合适?设太小 → 报文其实还在路上就误判丢包,造成无谓重传、加剧拥塞;设太大 → 真丢包时要等很久,延迟飙升。所以 RTO 必须自适应地跟随网络 RTT 变化。
RTT 与 RTO 的计算
- RTT(Round-Trip Time):从发送到收到对应 ACK 的往返时间。
- SRTT(平滑 RTT):
SRTT = (1-α)·SRTT + α·RTT,α 通常取 1/8,做指数加权平均。
- RTTVAR(RTT 偏差):衡量 RTT 波动程度,波动越大 RTO 越保守。
- RTO:
RTO = SRTT + 4·RTTVAR(Jacobson/Karels 算法),并设定上下限。
Karn 算法:如果报文发生了重传,就不应该用这次的 ACK 来更新 RTT——因为你分不清这个 ACK 是回应原始报文还是重传报文。解决办法:重传时不更新 RTT,并且退避加倍(每重传一次 RTO ×2)。
④快速重传:不等超时,3 个重复 ACK 就重发
超时重传要等一个完整 RTO,太慢了。快速重传(Fast Retransmit)的思路是:如果接收方连续收到 3 个相同的重复 ACK,说明对应的报文大概率丢了(后面的报文都到了,就它没到),发送方立刻重传,不用等计时器。
SACK:只重传真正丢的
快速重传仍有浪费:发送方只知道 1001 丢了,但不知道 2001 之后的报文到底有没有到,可能要把后面的一起重传。SACK(Selective Acknowledgment,选择性确认)让接收方在 ACK 里额外携带“我已收到的不连续区间”,发送方就能只重传真正丢失的那几段。
SACK 效果举例:丢了 seq=1001 这一段,但 2001~5000 都收到了。没有 SACK 时可能要重传 1001 及之后全部;有了 SACK,接收方能告知“我已有 2001~5000”,发送方只需重传 1001~2000。SACK 通过 TCP 头部选项字段协商启用。
⑤滑动窗口:为什么能一边发一边等
如果每发一个报文都要等 ACK 才能发下一个(“停止-等待”),吞吐量会惨不忍睹。滑动窗口允许发送方连续发送多个未被确认的报文,把等待时间重叠起来,大幅提升吞吐。
要点:发送窗口不是固定不动的。每收到一个 ACK,左边界就右移,窗口整体向前“滑动”,从而允许发送新的数据。被确认过的数据会从发送缓冲区释放。
⑥流量控制:别把接收方冲垮
接收方处理速度有限。如果发送方发得太快,接收方的缓冲区会被填满,后续报文只能丢弃 → 触发重传 → 更拥堵。所以 TCP 用接收窗口(rwnd, receive window)做流控。
核心机制:接收方在每个 ACK 里带上自己的剩余缓冲区大小 rwnd。发送方保证未被确认的数据量 ≤ rwnd。rwnd 由 TCP 头部的「窗口大小」字段承载。
零窗口与糊涂窗口综合症
零窗口(Zero Window)
rwnd=0 时发送方停止发送,并启动持续计时器周期性发窗口探测报文,防止“窗口更新报文丢失导致双方死锁”。
糊涂窗口综合症(SWS)
接收方每次只腾出几个字节就通告,发送方就发几个字节的小包,有效载荷极低。解决办法:接收方等腾出足够空间(MSS 或缓冲区一半)再通告;发送方攒够再发(Nagle)。
最终发送窗口 = min(拥塞窗口 cwnd, 接收窗口 rwnd)。发送方同时受网络拥塞和接收方能力两个约束,取更严格的一个。
⑦面试高频追问
Q1:TCP 如何保证可靠传输?
靠
序列号 + 确认应答 + 重传(超时重传与快速重传)保证不丢;靠
序列号排序保证有序、去重;靠
校验和检测损坏;靠
滑动窗口 + 流量控制匹配收发速度;靠
拥塞控制避免压垮网络。
Q2:ack=N 到底是什么意思?
“N 之前的所有字节都已收到,期待下一个是第 N 字节”。即
累积确认,表示期望收到的下一个序号,不是已收到的最后一个。
Q3:有了超时重传为什么还要快速重传?
超时重传要等一个完整 RTO,
延迟太大。快速重传用「3 个重复 ACK」判断丢包,
不必等计时器就立即重传,显著降低丢包时的延迟。
Q4:为什么是“3 个重复 ACK”而不是 2 个?
这是
经验折中:1~2 个重复 ACK 很可能只是
乱序(报文晚到一会儿),3 个重复 ACK 基本可以判定为
丢包。取 3 能在误判率和响应速度之间取得平衡。
Q5:SACK 解决了什么问题?
解决
“不知道后面哪些到了”的问题。累积 ACK 只能表达连续收到的部分,SACK 允许接收方告知已收到的
不连续区间,发送方从而
只重传真正丢失的段,避免无谓重传。
Q6:流量控制和拥塞控制的区别?
流量控制是
端到端的,保护
接收方不被冲垮,依据是接收方通告的
rwnd;
拥塞控制是
全局的,保护
网络不被挤爆,依据是发送方自己探测到的
cwnd。最终发送窗口取二者最小值。
Q7:为什么需要滑动窗口,停等不行吗?
停等协议每个报文都要等一个 RTT 才能发下一个,
信道利用率极低(尤其长肥管道)。滑动窗口允许连续发送多个未确认报文,把等待时间重叠,吞吐量成倍提升。
Q8:RTO 是固定值吗?怎么算?
不是,是
自适应的。基于测量的 RTT 计算平滑值 SRTT 与偏差 RTTVAR,
RTO = SRTT + 4·RTTVAR;发生重传时按 Karn 算法
不更新 RTT 并
指数退避。
✓一句话速记
可靠 = 编号(seq)+ 确认(ack 是期待下一字节)+ 重传(超时 RTO / 快速 3 个重复 ACK / SACK 选择性重传);效率 = 滑动窗口连续发;安全 = 流量控制(rwnd,护接收方)+ 拥塞控制(cwnd,护网络),发送窗口取 min。