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 发送了
实验反映的现象已经和 《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 稍稍大一点。
比如:
当然,这只是我们自己臆想的公式,说不定,TCP 一开始创造出来的时候,RTO 真的是这么算的呢?
2.1 RTT 测量
可是,在公式中,RTT 是如何测量呢?在 TCP 中,每一次数据包传送过去到接收到对方的 ack 这个时间差,就会被 TCP 记录,然后保存到一个变量
在局域网中,我们的网络一般是很稳定的,每次重新计算一个 RTT,基本上变化不太大,但是在广域网中,网络就会变得异常复杂,这一次的 RTT 为 100ms,说不定下一次就变 800 ms 了,这时候,采用实时计算的 RTT 就会不合理,在 RFC 中,使用了加权的 RTT。它的公式如下:
RFC 2988 建议
举个例子,当前
2.2 Δt 怎么定义
在前面,我们臆想了一个公式:
现在我们将其更新为
因此,按照上面的定义,
实际上,
RFC 推荐
2.3 指数退避
假设在某一次发送数据的时候,数据丢失了,根据前面的公式,我们计算出了一个 RTO 值。如果和重传后,还是没有等到对方的 ack,那么 RTO 的值就会翻倍。只要重传的的数据没有 ack,那么 RTO 就会一直翻倍。
则第 n 次重传的
2.3 Karn 算法
假设一个分组被发送,经过若干次和重传后,收到了对方的 ack,则新的 RTT 如何计算呢?实际上,我们根本没有办法知道这个 ack 是对哪一次重传数据的确认,因此,Karn 算法规定:此时不更新
如果下一次再发生重传,使用退避后的 RTO 的值。
3. 回到图 1
在图 1 中,我们发现,第一次重传的
另一方面,如果我们按照前面的
#define TCP_RTO_MAX ((unsigned)(120*HZ)) // 120秒 #define TCP_RTO_MIN ((unsigned)(HZ/5)) // 0.2s
另一方面,为什么 Linux 中的 TCP 重传 8 次后就在那停住,还得去看内核到底是怎么实现的了。相信大家在掌握了基本的 TCP 超时原理后,一定会找出这个答案的。
4. 总结
- 理解超时重传时间如何计算