先解决最笨的方案:停等
最朴素的可靠传输:发一个包,停下来等确认,收到了再发下一个。可靠是可靠,但一个 RTT 只能送一个包——RTT 50ms 的链路,每秒最多 20 个包,带宽利用率为零点几,线路基本在闲置。问题不在发得太快,而在同时在线的包太少,喂不饱管道。
滑动窗口:在途数据封顶
滑动窗口的思路:允许一批包同时在路上,只要未确认的字节数不超过窗口大小。收到确认,窗口向右滑,后面的数据继续上路:
发送缓冲区(seq 表示字节序号):
[1..4 已确认][5..12 在途中=窗口][13..24 排队等着][25.. 未来]
收到 ack=9(前 8 字节确认):
[1..8 已确认][9..16 在途中][17..24 排队][25.. 未来]
└── 窗口右滑 4 格,17..20 立刻发出去
// 窗口大小 = 同时允许在途的最大字节数
// 吞吐量 ≈ 窗口大小 / RTT ← 这就是窗口调优的理论基础记住这条公式:吞吐量 ≈ 窗口 / RTT。跨洋链路 RTT 200ms,想要 100MB/s 吞吐,窗口得开到 20MB——这就是后面长肥管道调参的依据。
丢了怎么办:三重保险
超时重传:发出后启动定时器,超时没等到确认就重发,并指数退避(1s、2s、4s……),同时窗口收缩——超时通常意味着网络堵了,先冷静。快速重传:收方收到失序包会重复确认最后连续的字节,发方连收 3 个重复 ACK 就判定那个包丢了(等不到超时那么久),立刻重发。SACK(选择性确认):普通 ACK 只会说“我收到的连续到第几字节”,SACK 附加信息里能列出“另外我还零散收到了哪些段”,发方只需补真正的空洞,不用盲目重发一整窗。
窗口大小谁说了算
接收方在每个 ACK 里广播自己的接收窗口 rwnd——“我的缓冲区还剩多少,你别发超过它”。发方在途数据量不得超过 rwnd。这个字段解决了接收方处理不过来的问题;但网络中间堵不堵,接收方不知道——那道闸叫拥塞窗口 cwnd,是下一篇的主角。两道闸取最小值:实际发送量 = min(rwnd, cwnd)。
实战:亲眼看看窗口
$ ss -tin dport = :443
ESTAB 0 0 10.0.0.5:52333 203.0.113.10:443
cubic wscale:7,7 rto:204 rtt:12.5/6.2 ato:40 mss:1448
cwnd:38 ssthresh:38 send 44.5Mbps unacked:38 rcv_space:29200
// cwnd:38 → 拥塞窗口 38 个报文段(约 54KB)
// rtt:12.5 → 往返 12.5ms,估算吞吐 54KB / 12.5ms ≈ 44Mbps(send 字段)
// unacked:38 → 正好在途 38 个包,窗口顶满——吞吐被 cwnd 卡住了排查传输慢时先看 ss -i:rtt 大、cwnd 顶满、unacked 等于窗口,说明瓶颈在链路带宽或拥塞;retrans 字段高,说明丢包严重。下一篇讲另一道闸:拥塞控制——TCP 怎么在不知道网络状况的情况下,摸出一道路况来。
评论 (0)