-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a1a744d
commit 69979f6
Showing
1 changed file
with
63 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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的第二步)。 |