Skip to content

TCP 可靠传输与序号机制

2026 大纲 五(三)3 TCP 可靠传输。RTO 的递推公式与 Karn 算法、SACK 的容量推导、发送窗口的三条边界与滑动规则集中在本篇;rwnd 怎么产生见《TCP 流量控制》,cwnd 怎么变见《TCP 拥塞控制》。

一、四根支柱,序号是地基

IP 只承诺尽最大努力交付:会丢、会错、会重复、会失序。TCP 要在这样一条信道上做出"无差错、不丢失、不重复、按序到达",靠的是四样东西——

字节编号 + 累积确认:解决乱序、重复,并发现丢失。 超时重传:把丢的数据补回来。 校验和:对付比特差错(算法与 UDP 完全相同,见《UDP 协议》)。 滑动窗口:提高效率,同时承载流量控制与拥塞控制。

其中序号是地基:没有它,确认、排序、去重、窗口全都建不起来。

累积确认与它可以量化的代价

确认号 ack 的语义是期望收到的下一个字节的编号,隐含"之前的所有字节都已正确收到"。TCP 要求接收方必须有累积确认的功能。

这套语义容错性很好——中间丢了一个确认,后面的确认会把它一起捎上。但它不够精确,而且代价可以算出来:接收方收到了 1~500 与 601~700,却只能回一个 ack=501;发送方无从知道 601~700 已经到了,丢 100 字节最坏要重传 200 字节

补这一格的是 SACK(选择确认),第四节展开。

确认还有两条时限要记:确认推迟的时间不应超过 0.5 秒收到一连串具有最大长度的报文段时,必须每隔一个报文段就发送一个确认。另有一条与直觉相反的事实——捎带确认实际上并不经常发生,因为大多数应用很少同时在两个方向上发数据。

交互可视化

加载可视化中...

二、发送窗口:四段、三个指针

发送缓存里的字节被三条分界线切成四段:

      已确认      │  已发送未确认  │  可以发送  │   不能发送
 ─────────────────┤◄──────────── 发送窗口 ────────────►├──────────
                 后沿                              前沿

已确认的可以从发送缓存中删除;已发送未确认在收到确认之前必须暂时保留,以备超时重传可以发送的是窗口内还没发的;不能发送的在窗口之外——因为接收方没有为这部分数据保留缓存空间

三条分界线就是教材的三个指针,都指向字节的序号P1 是发送窗口的后沿P2 是"已发送未确认"与"可以发送"的分界,P3 是发送窗口的前沿。三个差值各有含义:

P3P1=发送窗口,P2P1=已发送但尚未收到确认,P3P2=可用窗口

自检恒等式:(P2P1)+(P3P2)=P3P1

推动三个指针的力量各不相同,这一条最值得单独记P1 只被收到的确认推动P2 只被自己发出的数据推动P3P1 加对方通告的窗口值决定。所以"窗口滑动"(后沿前移)与"窗口张缩"(前沿位置变化)是两件独立的事

拿一个小例子对一遍:某时刻 P1=100P2=140P3=200——发送窗口 100 字节,已发送未确认 40 字节,可用窗口 60 字节。此后收到 ack=121, rwnd=60:后沿前移到 P1=121,前沿变成 P3=121+60=181,而 P2 不动,仍是 140(确认只回收已发出的数据,并不改变"我已经发到哪儿了")。新的发送窗口 181121=60、已发送未确认 140121=19、可用窗口 181140=41,且 19+41=60 ✓。

可用窗口 = 发送窗口 − 已发送未确认的字节数,它才是"现在还能发多少"。窗口满了(P2P3 重合)不代表窗口变小,只是位置都被占着,要等确认回来使 P1 前移才腾得出来。

两条边界规则

后沿绝不可能左移——左移等于撤销已收到的确认,逻辑上不可能。

前沿可以收缩,但标准强烈不赞成——逻辑上可能(对方通告的窗口变小了),但发送方很可能已经把那部分数据发出去了,会产生错误

前沿"不动"有两种情况:没收到新确认且对方通知的窗口不变;或收到了新确认但对方通知的窗口缩小了,两个变化恰好抵消。

三处容易混的边界

缓存不等于窗口。 发送缓存存的是"待发的 + 已发未确认的",发送窗口通常只是发送缓存的一部分,两者后沿重合;接收缓存存的是"按序到达但尚未被读取的 + 未按序到达的"。

TCP 的失序处理更像 SR 而不是 GBN。 教材写得很谨慎:对于不按序到达的数据应如何处理,TCP 标准并无明确规定,但 TCP 通常先临时存放在接收窗口中,等缺少的字节收到后再按序交付。确认方式像 GBN、重传行为却接近 SR

A 的发送窗口不总等于 B 的接收窗口。 两个原因:① 窗口值经网络传送有时间滞后;② A 还可能按网络拥塞情况主动减小。这正是那个公式的来历——

发送窗口=min(cwnd, rwnd)

rwnd 代表"对方接不接得下",cwnd 代表"网络扛不扛得住",两个约束同时成立才能发。

发送窗口滑动的逐步走查,与数据链路层滑动窗口的对照(想看清后沿、前沿各被什么推动时展开)

设 B 通告 rwnd=20、ack=31("到序号 30 为止我都收到了,从 31 开始还能收 20 个字节")。

时刻事件后沿前沿窗口内已发未确认可用窗口
t0A 收到 ack=31, rwnd=20,构造发送窗口315131~50,共 20
t1A 发送序号 31~41 共 11 字节315131~4142~50,共 9
t2A 再发送 42~50 共 9 字节315131~500(窗口已满)
t3收到 ack=34, rwnd=20(31~33 已确认)345434~5051~53,共 3
t4收到 ack=34, rwnd=10(窗口缩小)344434~43(44~50 变成"不能发送")0

t4 就是"前沿向后收缩"的情形——TCP 标准强烈不赞成,因为 A 在 t2 时已经把 44~50 发出去了。

这张表同时解释三件事:① 窗口"滑动"的动力只来自收到确认(后沿前移);② 窗口"张大缩小"的动力来自对方通告的窗口值(前沿位置);③ 可用窗口 = 窗口大小 − 已发送未确认的字节数

与数据链路层滑动窗口的区别:

对比项数据链路层TCP
窗口单位字节
窗口大小通常固定动态调整(受 rwnd 与 cwnd 双重约束)
编号空间较小(如 3 位 = 0~7),窗口上限受 2n 约束32 位(02321),实际不受编号位数限制
失序数据GBN 一律丢弃,SR 缓存通常缓存(标准未强制规定)
确认方式逐帧确认或累积确认累积确认(+ 可选 SACK)

三、超时重传时间怎么定

重传时间的选择是 TCP 最复杂的问题之一。 RTO 两头的后果都很实在:太短会引起很多不必要的重传,使网络负荷增大;太长又使网络的空闲时间增大,降低传输效率

三个式子:

RTTS=(1α)RTTS+α新样本(α=1/8)RTTD=(1β)RTTD+β|RTTS新样本|(β=1/4)RTO=RTTS+4×RTTD

首次测量不套递推式RTTS 直接取所测量到的样本值,RTTD 取样本值的一半。而每一轮的计算顺序是先更新 RTTS,再用新值算 RTTD——反了整条链就全错。

光有平均 RTT 为什么不够

两条链路:的 RTT 稳定在 100 ms 上下、波动 ±2 ms;平均也是 100 ms,但在 20 ms 与 180 ms 之间剧烈跳。两者的 RTTS 完全一样,但乙需要宽得多的余量才不会误判超时。

"平均值"描述不了"抖不抖",所以要再引入描述抖动的 RTTD。教材对 RTO 的定性是"应略大于加权平均往返时间 RTTS",那个 4 倍偏差就是"略大于"的具体化——链路越抖余量越宽,越稳则 RTO 越贴近 RTTS

α 取偏小的 1/8 同理:RTT 样本噪声很大,α 大了 RTO 会跟着噪声乱跳;1/8 相当于"参考最近 8 次左右的历史",既跟得上趋势又不被单次抖动带偏(α 接近 0 更新慢、接近 1 更新快)。

重传把测量搅乱了:Karn 算法

重传的报文段和原来的完全一样,收到确认后源主机无法判断它在确认哪一次发送。两种误判都是发散的:把"对重传的确认"当成"对原报文段的确认",样本偏大(整个超时时间被算了进去),RTO 越来越长、丢包后只能干等;反过来样本偏小,RTO 越来越短、报文段过多地重传。一旦偏了,下一次更容易再偏同一个方向。

Karn 算法有三件事,缺一不可:① 只要报文段重传了,就不采用其往返时间样本;② 每重传一次就把 RTO 增大一些,典型做法是取旧值的 2 倍;③ 当不再发生重传时才按公式重新计算。

第 ② 条不是补丁,是必需的。 只做第 ① 条会出新问题:时延突然增大时样本全被丢弃,RTO 卡在旧值上,于是每个报文段都超时、都重传、样本又全被丢弃——死循环。指数退避正是为此:既然测不到新样本,那就不测了——直接假定网络变慢,把 RTO 翻倍,翻到某一次终于不用重传,测量随即恢复正常。翻倍而非"加固定值",是因为加固定值在网络严重恶化时追不上。

Karn 丢弃的是"样本"不是"确认":重传报文段的确认照样有效(数据确实到了、窗口照样滑动),只是该 RTT 样本不用来更新 RTTS

更彻底的办法是给每次发送打上时间标签,即时间戳选项的第一个功能——有了它重传报文段的样本也能安全使用

RTO 的四轮完整递推(想手动跑一遍两条递推式、看清偏差项怎么收窄时展开)

依次测得四个有效的 RTT 样本:40 ms、60 ms、30 ms、50 ms,取 α=1/8β=1/4RTTD 的更新使用本次更新后RTTS

第 1 步:第一个样本用初值规定,不套递推式。

RTTS=40,RTTD=402=20,RTO=40+4×20=120 ms

递推式里要用"旧值",而此刻还没有旧值。直接套公式是这类题最容易踩的坑。

第 2 步:第二个样本 60 ms。

RTTS=78×40+18×60=35+7.5=42.5RTTD=34×20+14×|42.560|=15+17.54=15+4.375=19.375RTO=42.5+4×19.375=42.5+77.5=120 ms

偏差式里的 |RTTS样本| 要用本轮更新后RTTS,所以必须先算 RTTS 再算 RTTD

第 3 步:第三个样本 30 ms。

RTTS=78×42.5+18×30=37.1875+3.75=40.9375RTTD=34×19.375+14×|40.937530|=14.53125+2.734375=17.265625RTO=40.9375+4×17.265625=110 ms

第 4 步:第四个样本 50 ms。

RTTS=78×40.9375+18×50=35.8203125+6.25=42.0703125RTTD=34×17.265625+14×|42.070312550|=12.94921875+1.982421875=14.93164RTO=42.0703125+4×14.93164=101.797 ms
次序RTT 样本RTTSRTTDRTO
1404020120
26042.519.375120
33040.937517.2656110
45042.070314.9316101.797

验证趋势方向。 四个样本在 30~60 之间摆动,RTTS 稳定在 42 上下,RTTD 从 20 一路降到 14.9——样本围绕均值摆动、并没有越来越离谱,"抖动估计"随之收窄,RTO 也从 120 降到 102。这个方向对了,说明整条递推没算错。 反过来若样本越来越极端,RTTD 会变大、RTO 会拉宽——这正是引入偏差项想要的自适应行为。

一处口径提醒:教材式 (5-6) 写的是 |RTTS新的 RTT 样本|,其中的 RTTS 通常按本轮更新后的值理解(本篇即按此计算)。若题面明确要求用更新前的旧值,按题面走即可,方法完全一样,只是代入的数不同。

四、SACK 与两条丢包判据

SACK 补的是累积确认那个"丢 100 要重传 200"的缺口:它在选项里报告已经收到的那些不连续字节块的边界。

边界有个差一规则要记准左边界指出字节块的第一个字节的序号,但右边界减 1 才是最后一个序号——收到 1501~3000 时右边界写 3001。这与"确认号是期望收到的下一个"其实是同一条规则的两个面。

最多报 4 个块:每个边界 4 字节、每块 8 字节,另加 2 字节种类与长度,2+8k40k4(5 块要 42 字节,超过选项长度上限)。

三条现实边界别忽略:① 建连时双方必须商定"允许 SACK";② 确认号字段的用法仍然不变(SACK 是附加信息,不是替代品);③ 文档没有指明发送方应当怎样响应 SACK,因此大多数实现还是重传所有未被确认的数据块

最后一条是本节的落点:TCP 有两条互相独立的丢包判据。

超时是强信号——可能网络严重拥塞。收到 3 个重复确认是弱信号——只是个别报文段丢了,后面的都到了,说明网络还通着。

两者触发的动作不同:超时触发超时重传,3 个重复确认触发快速重传(不等计时器到期,立即重传那个缺失的报文段)。

而且同一个丢包事件会同时触发两套机制可靠传输负责把丢的补回来,拥塞控制负责以后发慢一点。两条判据强弱不同,拥塞控制那边的反应也就不同——这是下一篇的内容。

本节小结

  1. 序号是地基,确认、排序、去重、窗口都建在它上面。累积确认容错性好但不够精确——丢 100 字节最坏要重传 200 字节,这一格由 SACK 补上(右边界减 1 才是块内最后一个序号,最多报 4 个块)。
  2. 发送窗口的动力学P1 由收到的确认推动、P2 由自己发出的数据推动、P3P1 加对方通告的窗口值决定;后沿绝不可能左移,前沿收缩标准强烈不赞成;可用窗口 = 发送窗口 − 已发送未确认。
  3. RTO 光有均值不够,均值相同而抖动不同的链路需要不同余量,故取 RTO=RTTS+4RTTD首次测量直接赋初值、每轮先更新 RTTS 再算 RTTD。重传使"这个 ACK 对应哪次发送"无法判断,故 Karn 不采用重传样本 + 每重传一次 RTO 翻倍 + 不再重传后恢复计算,三件事缺一不可。

考点速记

本篇真题里被考过的形式有两种,一种算确认号,一种算"还能发多少"。两种都只用第一、二节那几条规则,一条公式都不用背。

A 组:给几个报文段,算确认号。三道题层层加码。

cn-2009-38 是基准款:甲连发两段,载荷 300 B 和 500 B,第一段序号 200,乙两段都正确收到,问确认号。

第一段占 200499,第二段占 500999 → 乙期望的下一个字节是 1000,答 D唯一的动作是"序号 + 载荷长度",别在这里少减一或多加一:占用的最后一个字节是 999,而确认号填的是"期望的下一个",正好是 1000。

cn-2011-40 加了一个丢包:甲连发三段,载荷 300 / 400 / 500 B,第 3 段的序号是 900,乙只收到第 1 和第 3 段,问确认号。

先倒推前两段的序号:第 3 段 seq = 900,那么第 2 段占 500899(400 B)、seq = 500;第 1 段占 200499(300 B)、seq = 200。

再用累积确认的语义判:乙收到了第 1 段(200499)和第 3 段(9001399),但中间的 500899 没收到。累积确认只能确认到第一个空洞之前——ack = 500,答 B

这道题是"累积确认代价"那一条的直接考查:第 3 段明明已经收到了,却一个字节都确认不了。选项里的 1400 就是给"把收到的都算上"的人准备的。

cn-2013-39 换成双向:甲收到乙的一个段,seq = 1913、ack_seq = 2046、载荷 100 B,问甲立即发给乙的段的序号和确认序号。

两个字段各查各的,别串。甲的 seq 由乙的 ack_seq 决定——乙说"我期望 2046",那甲接下来就该从 2046 发。甲的 ack_seq 由乙的 seq + 载荷长度决定——乙那段占 19132012,甲期望的下一个是 2013。答 B(2046、2013)

四个选项正是 2046/2047 × 2012/2013 的笛卡儿积,两处都要 +1 或都不 +1 的人各错一半。判据只有一句:ack 填"期望的下一个",seq 填"我要发的第一个"

B 组:给一张时序图,问"还能发多少 / 什么时候重传"。

cn-2021-40:甲在 t0 发出 seq=501 的段(200 B),t1 收到乙的 seq=601, ack_seq=501, rcvwnd=500,问甲在收到新确认之前可以继续发送的数据序号范围

关键在读懂 ack_seq=501:它表示乙期望 501,也就是说甲刚发出去的 501~700 还没有被确认

于是套第二节那三个指针:发送窗口左沿 = 501(等于 ack_seq),窗口大小 = rcvwnd = 500 → 窗口覆盖 5011000已发送未确认 = 501~700可用窗口 = 701~1000。答 C

选项 A(501~1000)是把整个窗口当成"还能发",忘了扣掉已发未确认的部分;B 和 D 则是把窗口左沿错当成了乙的 seq 601——乙的 seq 是它自己发数据的序号,与甲的窗口毫无关系,这是全题最刻意的一处干扰。

cn-2019-38 考的是快速重传的触发点:客户在 t0 第一次收到 ack_seq=100 并发出 seq=100 的段,该段丢失,问支持快速重传时客户重发该段的时刻。

数重复确认要从"第一次之后"开始数。 t0 那个 ack=100新确认,不算重复;此后 t1 是第 1 个重复、t2 是第 2 个、t3 是第 3 个重复确认——凑够 3 个,立即快速重传。答 C

这道题唯一的坑就是把 t0 也数成重复,那样会答成 t2规则是"收到 3 个重复确认",而重复是相对于第一次而言的。

本篇练习区里还会出现三道题,机制讲在别处:cn-2015-39 与 cn-2025-38 考接收窗口封顶与拥塞窗口增长,讲在 TCP 流量控制拥塞控制逐轮推演;cn-2017-39 考慢开始涨到指定窗口要多久,讲在 TCP 拥塞控制

本篇的 RTO 递推式与 Karn 算法在真题里不单独成题,但它们回答的是"重传计时器凭什么定成那个值"——知道 RTO=RTTS+4RTTD 里那个 4 倍是"链路越抖余量越宽",就不会把超时和 3 个重复确认这两条判据混为一谈。

易错确认号填"期望收到的下一个字节",等于"最后一个已收字节 + 1"。

易错累积确认只能确认到第一个空洞之前。 空洞之后即使收到了也确认不了。

易错双向通信时 seq 和 ack 各查各的:我的 seq 来自对方的 ack_seq,我的 ack 来自对方的 seq + 载荷长度。

易错发送窗口的左沿是 ack_seq,不是对方的 seq。 对方的 seq 是它自己发数据用的。

易错"还能发多少" = 窗口大小 − 已发送未确认,不是整个窗口。

易错快速重传要 3 个重复确认,第一次收到的那个不算重复。

易错后沿绝不可能左移(撤销不了已收到的确认);前沿收缩逻辑上可能但标准强烈不赞成。

易错Karn 丢弃的是 RTT 样本,不是确认本身。 数据确实到了,窗口照样滑动。

易错RTO 首次测量不套递推式RTTS 取样本值、RTTD 取样本值的一半。

易错SACK 的右边界减 1 才是块内最后一个序号,收到 1501~3000 要写 3001。

教材出处
  • 谢希仁《计算机网络》(第 8 版)p230(5.6.1 以字节为单位的滑动窗口):发送窗口边界规则的原文——"凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用";"发送窗口前沿的前面部分表示不允许发送,因为接收方没有为这部分数据保留临时存放的缓存空间";"发送窗口后沿不可能向后移动,因为不能撤销掉已收到的确认。发送窗口前沿通常是不断向前移动的,但也有可能不动……发送窗口前沿也有可能向后收缩……但 TCP 的标准强烈不赞成这样做"。
  • 同书 p231(图 5-15 及正文):三个指针的定义与三个差值——"要描述一个发送窗口的状态需要三个指针:P1P2P3……指针都指向字节的序号。P1 之前的数据是已发送并已收到确认的部分。P3 之后的数据是不允许发送的部分。P3P1= A 的发送窗口……P2P1= 已发送但尚未收到确认的字节数……P3P2= 允许发送但当前尚未发送的字节数(又称为可用窗口或有效窗口)"。
  • 同书 p232(图 5-18 及正文):缓存与窗口的关系——发送缓存存放"发送应用程序传送给发送方 TCP 准备发送的数据"与"TCP 已发送出但尚未收到确认的数据","发送窗口通常只是发送缓存的一部分……发送缓存和发送窗口的后沿是重合的";接收缓存存放"按序到达的、但尚未被接收应用程序读取的数据"与"未按序到达的数据"。
  • 同书 p233:三点强调——"虽然 A 的发送窗口是根据 B 的接收窗口设置的,但在同一时刻,A 的发送窗口并不总是和 B 的接收窗口一样大"(原因是窗口值传送的时间滞后与发送方按拥塞情况的主动减小);"对于不按序到达的数据应如何处理,TCP 标准并无明确规定……因此 TCP 通常是把不按序到达的数据先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程";"TCP 要求接收方必须有累积确认的功能……TCP 标准规定,确认推迟的时间不应超过 0.5 秒。若收到一连串具有最大长度的报文段,则必须每隔一个报文段就发送一个确认……捎带确认实际上并不经常发生,因为大多数应用程序很少同时在两个方向上发送数据"。
  • 同书 p233–p234(5.6.2 超时重传时间的选择):定性与公式——"重传时间的选择却是 TCP 最复杂的问题之一";RTO 太短"会引起很多报文段的不必要的重传,使网络负荷增大"、太长"又使网络的空闲时间增大,降低了传输效率";式 (5-4) 的 α 语义"若 α 很接近于零,表示新的 RTTS 值和旧的 RTTS 值相比变化不大……若选择 α 接近于 1,则表示新的 RTTS 值受新的 RTT 样本的影响较大",推荐 α=1/8"每当第一次测量到 RTT 样本时,RTTS 值就取为所测量到的 RTT 样本值";式 (5-5) RTO=RTTS+4×RTTD 及"RTO 应略大于上面得出的加权平均往返时间 RTTS";式 (5-6) 及初值"当第一次测量时,RTTD 值取为所测量到的 RTT 样本值的一半",β 推荐 1/4。
  • 同书 p234:两种误判的完整推演——"若收到的确认是对重传报文段的确认,但却被源主机当成是对原来的报文段的确认,则这样计算出的 RTTS 和超时重传时间 RTO 就会偏大……同样,若收到的确认是对原来的报文段的确认,但被当成是对重传报文段的确认,则由此计算出的 RTTS 和 RTO 都会偏小。这就必然导致报文段过多地重传";Karn 算法原文"在计算加权平均 RTTS 时,只要报文段重传了,就不采用其往返时间样本";以及修正"报文段每重传一次,就把超时重传时间 RTO 增大一些。典型的做法是取新的重传时间为旧的重传时间的 2 倍。当不再发生报文段的重传时,才根据式 (5-5) 计算超时重传时间"。
  • 同书 p235(5.6.3 选择确认 SACK):边界的差一规则——"第一个字节块的左边界 L1=1501,但右边界 R1=3001 而不是 3000。这就是说,左边界指出字节块的第一个字节的序号,但右边界减 1 才是字节块的最后一个序号";容量推导——"由于首部选项的长度最多只有 40 字节,而指明一个边界就要用掉 4 字节……因此在选项中最多只能指明 4 个字节块的边界信息……如果要报告 5 个字节块的边界信息,那么至少需要 42 字节。这就超过了选项长度 40 字节的上限";三条边界条件——"在建立 TCP 连接时,就要在 TCP 首部的选项中加上'允许 SACK'的选项,而双方必须都事先商定好"、"原来首部中的'确认号字段'的用法仍然不变"、"SACK 文档并没有指明发送方应当怎样响应 SACK。因此大多数的实现还是重传所有未被确认的数据块"。

相关知识

TCP 协议基础TCP 流量控制滑动窗口协议对比

真题练习