28-TCP 协议(超时与重传)

TCP 超时与重传应该是 TCP 最复杂的部分之一了。Windows 和 Linux 对这部分的实现还有所不同,但是算法基本上还是差不多的。

超时重传是 TCP 保证可靠传输的基础。当 TCP 在发送数据时,数据和 ack 都有可能会丢失,因此,TCP 通过在发送时设置一个定时器来解决这种问题。如果定时器溢出还没有收到确认,它就重传数据。

无论是 Windows 还是 Linux,关键之处就在于超时和重传的策略,需要考虑两方面:

  • 超时时间设置
  • 重传的频率(次数)

目前来说,在 Linux 较高的内核版本中,比如 3.15 中,已经有了至少 9 个定时器:超时重传定时器,持续定时器,ER延迟定时器,PTO定时器,ACK延迟定时器,SYNACK定时器,保活定时器,FIN_WAIT2定时器,TIME_WAIT定时器。

这实在是太多了,对初学者来说,我们重点掌握以下 4 个:

  • 超时重传定时器(retransmit)
  • 持续定时器(persist)
  • 保活定时器(keepalive,这和 HTTP 协议中的 keepalive 不是同一个概念)
  • TIME_WAIT 定时器

1. 一个超时重传的例子


这里写图片描述
图1 超时重传

本实验所使用的程序路径为 unp/program/echo/processzombie/echo.cc,你可以直接使用 make 命令进行编译。

  • 服务器端启动方式
$ ./echo -s -h flower // 或者你可以这样写 ./echo -s -h 192.168.166.47,flower 是我其中一台 linux 主机的名字
  • 客户端启动方式
$ ./echo -h flower // 或者你可以写 ./echo -h 192.168.166.47

当客户端连接成功后,发送一行数据'helloworld',对方回射回来,一切正常,接下来,将服务器主机 flower 断网,然后客户端再次发送数据 'hehe'

大约等等了 16 分钟左右(图2),客户端返回一个错误:No route to host.


这里写图片描述
图2 客户端等待约 16 分钟后返回错误


这里写图片描述
图3 第 9 次重传后,主机亲自发送 ARP 协议询问对方 MAC 地址

做这个实验时,两个主机都属于同一个网段,有机会,我会将两个主机放到不同的网段再试一次,看看结果是否还是这样。因为在同一个网段,主机 sun 发送了 17×3=51 次 ARP 请求,每发 3 次 ARP 就等 50 s 左右。

实验反映的现象已经和 《TCP/IP 详解卷1:协议》(后面简称《详解》)不再一致。

《详解》中的第 21 章的例子(图 21-1),是在经历了 12 次重传后放弃(约 9 分钟),向对方发送 RST 段。

《详解》这本书由 W.Richard Stevens(1951-1999) 在 1993 年编写,时隔 24 年,TCP/IP 协议早已经历了无数次的演化,这和书上描述的现象不一致太正常了。然而,Stevens 先生不幸在 1999 年去逝(据说是攀岩失足?),这是计算机科学界和教育界最重大的损失。

虽然超时重传算法今非昔比,但是如果直接拿到现在所使用的算法来讲解,初学者也会因为太复杂而放弃学习,所以,还是按照 Stevens 先生在《详解》叙述的算法来学习吧!

2. 往返时间(RTT)

超时重传时间(Retransmission TimeOut, RTO)要怎么设置呢?

数据包过去,到 ack 返回,这个时间一般约等于 RTT 时间,如果一个 RTT 时间内没有收到 ack,很可能对方就没有收到数据,或者回送的 ack 丢失。

所以,最直观的想法是,RTO 应该比 RTT 稍稍大一点。

比如:

RTO=RTT+Δt

当然,这只是我们自己臆想的公式,说不定,TCP 一开始创造出来的时候,RTO 真的是这么算的呢?

2.1 RTT 测量

可是,在公式中,RTT 是如何测量呢?在 TCP 中,每一次数据包传送过去到接收到对方的 ack 这个时间差,就会被 TCP 记录,然后保存到一个变量 RTTs 中去。

在局域网中,我们的网络一般是很稳定的,每次重新计算一个 RTT,基本上变化不太大,但是在广域网中,网络就会变得异常复杂,这一次的 RTT 为 100ms,说不定下一次就变 800 ms 了,这时候,采用实时计算的 RTT 就会不合理,在 RFC 中,使用了加权的 RTT。它的公式如下:

RTTs=(1α)×RTTs+α×RTT

RFC 2988 建议 α=0.125。上面这个公式说明,我想使用旧的 RTTs 中的 87.5% 的成分,新的 RTT 样本中 12.5% 的成分,这样就不至于让加权的 RTTs 发生剧烈抖动。一个直观的例子就是,某些同学的成绩太差,拉低了班级的整体水平,为了防止这种现象,可以在计算平均值的时候动些手脚。

举个例子,当前 RTTs=200ms,最新一次测量的 RTT=800ms,那么更新后的 RTTs=200×0.875+800×0.125=275ms.

2.2 Δt 怎么定义

在前面,我们臆想了一个公式:

RTO=RTT+Δt

现在我们将其更新为

RTO=RTTs+Δt

RTTS 的计算方法我们在 2.1 中已经介绍过了,现在是 Δt 是什么,它怎么算?我们只知道,它应该很小。RFC 2988 规定:

RTO=RTTs+4×RTTD

因此,按照上面的定义,Δt=4×RTTD. 而 RTTD 计算公式如下:

RTTD=(1β)×RTTD+β×|RTTsRTT|

实际上,RTTD 就是一个均值偏差,它就相当于某个样本到其总体平均值的距离。这就好比你的成绩与你班级平均成绩差了多少。

RFC 推荐 β=0.25.

2.3 指数退避

假设在某一次发送数据的时候,数据丢失了,根据前面的公式,我们计算出了一个 RTO 值。如果和重传后,还是没有等到对方的 ack,那么 RTO 的值就会翻倍。只要重传的的数据没有 ack,那么 RTO 就会一直翻倍。

则第 n 次重传的 RTOn 值为:

RTOn=2n1×RTO1

2.3 Karn 算法

假设一个分组被发送,经过若干次和重传后,收到了对方的 ack,则新的 RTT 如何计算呢?实际上,我们根本没有办法知道这个 ack 是对哪一次重传数据的确认,因此,Karn 算法规定:此时不更新 RTTs 的值

如果下一次再发生重传,使用退避后的 RTO 的值。

3. 回到图 1

在图 1 中,我们发现,第一次重传的 RTTs=0.225s,而第二次,并没有使用指数退避算法,到了第 3 次重传才使用了指数退避算法,后面依次是 0.451, 0.902, 1.804, 3.612, 7.215, 14.416, 28.865。

另一方面,如果我们按照前面的 RTTs 计算公式,计算出来的 RTTs 肯定是非常小的,大概就只有几毫秒,如果根据这个来计算 RTO,肯定也几毫秒的样子。可是为什么重传超时时间的 RTO达到了约200ms? 现代的 Linus 内核中,规定了 RTO 有一个最大值和最小值,最大值是是 120 秒,最小是 200 ms:

#define TCP_RTO_MAX ((unsigned)(120*HZ))  // 120秒 #define TCP_RTO_MIN ((unsigned)(HZ/5))    // 0.2s

另一方面,为什么 Linux 中的 TCP 重传 8 次后就在那停住,还得去看内核到底是怎么实现的了。相信大家在掌握了基本的 TCP 超时原理后,一定会找出这个答案的。

4. 总结

  • 理解超时重传时间如何计算
说明:本文转自--Allen--,用于学习交流分享,仅代表原文作者观点。如有侵权,请联系我们删除~