TCP 的数据传输
TCP 建立于 IP 协议之上,而 IP 协议是一个尽力而为的协议,所以可靠性由 TCP 自己实现。TCP 将数据看成一个无结构的、有序的字节流,头部有序列号和确认号两个字段用于实现数据的可靠传输。
TCP 将报文段看成字节流,每个字节都有对应的编号,序列号记录的就是 报文段中字节流第一个字节的编号,而不是报文段的序号。例如传输一个大型的文件,MSS(最大报文段长度)为 1000,那么 TCP 会将其分为数个报文,第一个报文分配序号为 0,那么第二个是 1000,第三个 2000,依此类推。
确认号设立的目的是为了告诉发送方已收到对方的发送的数据。确认号的值为已收到的字节流的最后一个编号加一,即 期望收到的下一个字节的编号,通常确认报文称为 ACK。
通过这两个字段,对于发送方来说每次发送都应收到对应的 ACK,用这一机制告知发送方是否发送成功,从而需要启动重传机制。
发生重传的几种特殊情况
数据的发送和 ACK 的返回都会触发超时,对于一些特殊的情况 TCP 都能有较好的应对:
- 发送数据成功抵达接收方,返回 ACK 丢失。这种情况依旧会触发超时机制,发送方会重新发送这一数据报,而接收方接收到重复数据时会再次返回 ACK。
- 连续发送数据中,某一个 ACK 丢失,而下一个 ACK 抵达,由于第二个 ACK 号比第一个大,说明其实第一个 ACK 对应的数据成功抵达,所以发送方并不会为此重传。
这里第二种情况也另外说明了,其实接收方并非必须每个报文都返回 ACK,可以收到多个报文后统一回复一个 ACK,可以节约资源,这个方式称为 累计确认。
超时
在发送方发送后一段时间未收到 ACK 后会重传,这段时间称为 RTO(重传超时),设置过长会导致等待时间过长,网络利用率降低,设置过短则会在 ACK 抵达之前就重传,导致传送许多不必要的重复数据。TCP 中 RTO 会根据 RTT(往返时间)动态设置。
标准方法中用平均偏差和测量的 RTT 来对 RTO 进行调整。其中 srtt 为平滑的 RTT 估计值,赋予当前的 srtt 和新测量的 RTT 不同权重以更新 srtt,再利用平均偏差 rttvar 来设置 RTO。
\[srtt = (1-g)srtt + g * RTT \\ rttvar = (1-h)rttvar + h * \vert RTT - srtt\vert \\ RTO = srtt + 4 * rttvar\]有时候会在 TCP 时钟粒度(一个“滴答”的时间)和 rttvar*4 中选择一个较大的值来更新 RTO,这样可以设立一个最小值,同时也会直接对 RTO 赋予最大值,例如
\[RTO = max(srtt + max(G, 4*rttvar), 1000)\]在刚开始连接时并没有 RTT 的测量值,这时 RTO 初始值一般设为 1s,当收到第一个 RTT 测量值时,srtt 初始化为 RTT,rttvar 初始化为 RTT/2。
基于计时器的重传
有了 RTO 之后就可以合理设置计时器的超时时间,TCP 会为每个报文设立重传计时器,当收到 ACK 后就会取消。
在发生超时重传时,说明网络条件不理想会发生退避机制,一是用拥塞控制机制减小窗口大小,二是利用 Karn 算法设置退避因子 \(\lambda\),让 RTO 暂时乘上 \(\lambda\),随着每次发生重传 \(\lambda\) 就会加倍增长:2、4、8,直到达到设定的最大值。如果收到 ACK,就重置为 1。
快速重传
基于计时器有时会导致等待过长时间,快速重传机制可以用来更及时的修复问题。触发快速重传机制的原因时收到了多个重复的 ACK,因为这种情况说明某个数据报丢失,而之后的数据报抵达,所以期望收到的是同一个数据报。在这种情况下不必等到计时器超时就可以重新发送可能丢失的数据报。收到的重复 ACK 数量触发快速重传有个阈值,一般设置为 3,非标准化实现可能会对此动态调整。
带选择确认的重传
在 TCP 中有一个 SACK 选项用于实现选择重传。在连续传输中,有时候也许第三个报文丢失,但之后的第四第五个报文都抵达了,这样利用 SACK 选项,发送方就可以只重传丢失的第三个报文而不必重发后面的数据报。
SACK 选项指定 n 个块长度需要 8n+2 个字节,又由于 SACK 需要与 TSOPT 一同使用,需要额外 10 个字节,TCP 选项最多 40 字节,所以每次最多填补 3 个空缺。