目录
[一 滑动窗口](#一 滑动窗口)
[1. Zero Window](#1. Zero Window)
[2. Silly Window Syndrome](#2. Silly Window Syndrome)
[二 SACK/DSACK](#二 SACK/DSACK)
[1. SACK](#1. SACK)
[2. DRACK](#2. DRACK)
[三 拥塞控制](#三 拥塞控制)
[1. Congestion Avoidance](#1. Congestion Avoidance)
[2. TCP New Reno](#2. TCP New Reno)
一 滑动窗口
1. Zero Window
对端通告窗口大小为 0 ,接收端就不能在发送数据。
发送方会尝试探测对端窗口大小,即发送 ZWP (Zero Window Probe)报文,对端回复窗口大小,如果发多次 ZWP 得不到响应或者一直通告窗口为 0 则会关闭连接或者触发 RST 连接重置。
2. Silly Window Syndrome
对端通告窗口大小为 1,2,3.... 这种很小的窗口,发送端就只能发送很小的数据,但基于网络协议栈进行封包,TCP/IP最少报头都是 20 + 20,不携带选项,在携带这么少的数据,也就是 MSS,开销比较大。
- 接收端引起
可以采用 David D Clark's 方案,如果接收的数据之后的窗口小于某个阈值,则直接通告窗口大小为 0 ,后续接收端处理数据腾出的空间大于 MSS,或者 接收缓冲区一半已经空闲,或者达到某个阈值,才会重新通告发送端窗口值。
- 发送端引起
可以采用 Nagle's algorithm 方案,如果对端已经收到所有的报文或者刚连接什么也没发,也就是发送的报文全部都 ACK 了,则无论多大的包都直接发送,否则如果还有的报文没有 ACK,后续写的数据会积攒起来,如果积攒超过 MSS 则会打包发过去,或者前面发的所有包都 ACK 了也会直接发出去。
二 SACK/DSACK
1. SACK
- 触发超时重传,中间丢了多个包的,具体是重发一个还是都发?所以引入了 SACK。
SACK:需要双方握手的时候携带选项进行协商。
- SACK 会记录已收到的非连续的报文序号,这样对端就能通过 ACK 和 SACK 得到哪些包丢了,比如:发 1000 2000 3000 4000 5000 6000,2000 3000 丢了,收到 1000,4-5-6000,ACK 3 次 2001,触发超时重传。
- SACK:4 - 6000 收到了
- ACK: 2001 之前的收到了
- 发送端根据 ACK 和 SACK 把 2000-3000 重发。
缺点:
上述的 SACK 的数据包只是辅助重传哪些包,但不是真正的 ACK,可能因为某些原因,比如内存不足,接收缓冲区严重不足,回应 RACK 的时候把 RACK 的这些包丢弃了,因为不是正真确定的 ACK 的包。
2. DRACK
- RACK 的一个子集,如果 RACK 的第一个字段被 ACK 覆盖 或者 第一个字段被 RACK 的第二个字段覆盖,那这个 RACK 就是 DRACK ,作用通知接收端哪些数据重发了
比如:
ACK丢包:发送 1,2,3。 1,2 的 ACK 丢了,触发超时重传,重传 1,但回复的是 ACK 3(3之前的收到了),DRACK 1(重复收到1) 。
网络延迟:发送 1,2,3,4,5。 2 因为延迟,导致3,4,5先收到触发超时重传,重传 2,后续回复 ACK 5,DRACK 为空。延时的到了 ACK 5,DRACK 2。
所以 DRACK 可以有效的表示:
- 发出的包丢了还是 ACK 的包丢了,ACK 丢了见上文,发出的包丢了根据后续报文回复的 ACK 即可。
- 因为自己 超时重传 时间短导致重复发送报文,比如发 1,ACK 还没到或者丢了就 重传了,接收端就会再次 ACK 1,SACK 1。
- 包乱序问题,先发的后收到,见上文。
SACK 和 DRACK 的值:SACK 的值会大于 ACK,RSACK 的值会小于 ACK。
三 拥塞控制
1. Congestion Avoidance
当拥塞窗口超过 ssthresh(慢启动阈值),则会进入 Congestion Avoidance(拥塞避免),后续拥塞窗口大小呈线性增加。
- 如果触发超时重传
最坏的情况:
- ssthresh = 当前窗口大小 / 2
- ssthresh = 1
随后继续进行慢启动。
如果触发快速重传
TCP Tahoe 的实现和超时重传一样。
TCP Reno 则会:
- 窗口大小 = 当前窗口大小 / 2
- ssthresh = 窗口大小
进入快速恢复阶段:
因为是快速重传,收到连续的 3 个 ACK,表示有 3 个包收到了,窗口大小 = ssthresh + 3 * MSS。
重传 ACK 指定的包,如果还是收到 ACK 指定的包则 窗口大小 = 窗口大小 + 1* MSS,如果收到新的 ACK,则 窗口大小 = ssthresh,随后进入拥塞避免阶段。
2. TCP New Reno
上述快速重传如果中间丢了多个包,只会重传第一个包,后面的则会进行超时重传,执行超时重传的解决逻辑,可以通过前面的 SACK 来动态发送。
这里引入了 TCP New Reno,在 TCP Reno 的基础上引入了 Partial ACK ,他会记录接收到的包的序列号最大的那一个,每次发送端重传数据包的时候和 Partial ACK 进行比较,小于就代表还有丢的包没重传,直到超过 Partial ACK 表示所有的包已收到。