Skip to content

Commit

Permalink
add TCP-超时计时器的管理.md
Browse files Browse the repository at this point in the history
  • Loading branch information
zhendewokusi committed Jan 5, 2024
1 parent a1a744d commit 69979f6
Showing 1 changed file with 63 additions and 0 deletions.
63 changes: 63 additions & 0 deletions source/_posts/TCP-超时计时器的管理.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: TCP 超时计时器的管理
date: 2024-01-05 15:54:19
tags: [TCP]
---

## 前言

本文只是一个简要的笔记,如果想要深入了解,请查阅[RFC 2988](https://www.rfc-editor.org/info/rfc2988)。适合对`TCP`有一定了解和基础的人查看。

## 每连接单一计时器

我们都能想到,如果给每一个`TCP`分段都分配一个计时器,这是最直接的方法。但是这带来了巨大的内存开销和调度开销。难不成一个`TCP`通信的服务器负担全用来给分段计时间,这太离谱了,有没有更好的办法,先人已经为我们想到了一个适用至今的方法:采取每个TCP连接单一计时器的设计。

首先我们要明确,这个超时计时器需要实现什么功能:
1. 有报文**长时间**没有接收到,必须提示超时。
2. 这个**长时间**不能太长也不能太短。

如果这里的超时时间太短了,会导致正常传输的数据包被当成无效数据,网络中充斥大量无效重传,浪费网络资源;如果超时时间太长,就会导致数据传输效率的太低,网络延迟提高。

因此RFC2988制定了下面的原则:
1. 每次发送包含数据的数据包(包括重传)时,如果定时器没有运行,则启动定时器。如果定时器在运行,就什么也不做。
2. 当所有未完成的数据都被确认后,关闭定时器。
3. 当接收到新数据的ACK时候,重新启动定时器。
当定时器超时时,执行下面的操作:
4. 指数退避,发送方设置 RTO 为之前的二倍,并且关闭定时器。
5. 重传 TCP 接收方尚未确认的最早的报文段。
6. 启动重传定时器。

为什么要有原则 3 ?

假设下面的情况,如果在定时器快到期的时候发送一部分数据。这样在超时前,只能收到定时器刚开始运行时发送的报文段,后续的正常数据包会被当作超时,因此后续的数据都需要重传,这太考验网络的负载和使用者的耐心了。有了原则3,好处自然不用我多说,保证一部分正常数据的不必要重传。

还有就是如果一个ack到来了,说明后续的ack大概率也会到达,即使出现了丢失,也会在两倍的 RTO 内被重传。为什么是两倍呢?举个简单的例子,A和B是两个同时发送的报文段的ACK(假设,一般会将其合并成一个ACK进行发送),发送方在无限接近超时时候收到了A,此时会将定时器重新启动,如果B数据包在网络中丢失,那么发送方再等待一个 RTO 就会知道 B 丢失。这样算下来不就是两倍的 RTO 内会被重传嘛。

## RTO的计算

为了计算RTO,TCP的发送方需要维护两个变量:SRTT(平滑往返时间,英文:Smoothed Round-Trip Time)和RTTAR(往返时间变化,英文:Returns the Round Trip Time Variance)。

SRTT、RTTAVR和RTO的计算规则如下:

1. 测量RTT之前,需要将RTO设置为3秒,此时指数退避依旧有效。
该计时器实际上会产生2.5秒到3秒的数值。因为使用粒度为G的心跳计时器的实现不应该将计时器设置低于`2.5 + G`秒。
2. 当进行第一个 RTT 测量时候,主机必须如下设置:
- SRTT <- R
- RTTVAR <- R/2
- RTO <- SRTT + max(G,K * RTTVAR) 这里的K为4
3. 当进行了一个后续的往返时间测量 R' 时,主机必须设置:
- RTTVAR <- (1 - beta) * RTTVAR + beta * | SRTT - R'|
- SRTT <- (1 - alpha) * SRTT + alpha * R'
这里的alpha为1/8,beta = 1/4。(2988里面说是 JK88 建议的,应该是个1988年的论文吧,没找到)。
这里需要注意的是,计算RTTVAR时用到的SRTT,必须是分配前的数值,也就是说,这两个计算的顺序不能改变。
计算 RTO 时候,如果其小于 1 秒,上取整。RTO可以设置最大值,但是最大值应该大于60秒。

## 获取 RTT 的样本

TCP**必须**使用[Karn-Partridge 算法](https://www.geeksforgeeks.org/karns-algorithm-for-optimizing-tcp/)来获取准确的消息往返时间估计。由于重传的模糊性,举个简单的例子,如果一个报文段发送了一次重传,发送方接受到ACK,但是此时的ACK到底是重传报文段的确认还是第一次发送的报文段的确认,这就是重传的模糊性,发送方不知道到底是哪个报文段的确认,这样得到的RTT会有很大的偏差,当然这个问题可以由TCP的时间戳选项来解决。该算法会忽略重传的数据段。仅使用明确的确认(即仅传送一次的段的确认)来估计往返时间。Karn 算法的第一部分规定,当存在重传模糊性时,RTT 值将被忽略,而不是集成到 SRTT 中。

但是也有问题,这么简单粗暴的方法,举个例子,如果TCP延迟显著增加后发送数据,TCP计算超时,并且根据之前的RTT来重新传输数据,极端情况下,TCP忽略所有的重传数据包的RTT,RTO永远不会更新。

其第二部分就是考虑到了这种不太理想的网络情况。为每次重传的RTO设置“退避因子”,也就是常说的指数退避。在不需要重传的成功数据传输发生之前,不会重置退避因子。该部分会放置网络的拥塞,使其能从任何拥塞问题中恢复,还保证了RTT信息不会丢失,当数据成功传输而无需重传时,可以将伴随的RTT测量添加到SRTT中。

需要注意的是:TCP 实现可以在多次回退计时器后清除 SRTT 和 RTTVAR,因为在这种情况下当前 SRTT 和 RTTVAR 很可能是假的。一旦 SRTT 和 RTTVAR 被清除,它们应该采集的下一个 RTT 样本进行初始化(RTO的第二步)。

0 comments on commit 69979f6

Please sign in to comment.