随着网络带宽时延产品 (BDP) 的增加,通常的 TCP 协议开始变的低效。这是因为它的 AIMD ( additive increase multiplicative decrease )算法彻底减少了 TCP 拥塞窗口,但不能快速的恢复可用带宽。理论上的流量分析表明 TCP 在 BDP 增加到很高的时候比较容易受包损失攻击。
另外,继承自 TCP 拥塞控制的不公平的 RTT 也成为在分布式数据密集程序中的严重问题。拥有不同 RTT 的并发 TCP 流将不公平地分享带宽。尽管在小的 BDP 网络中使用通常的 TCP 实现来相对平等的共享带宽,但在拥有大量 BDP 的网络中,通常的基于 TCP 的程序就必须承受严重的不公平的问题。这个 RTT 基于的算法严重的限制了其在广域网分布式计算的效率,例如: internet 上的网格计算。
一直到今天,对标准的 TCP 的提高一直都不能在高 BDP 环境中效率和公平性方面达到满意的程度(特别是基于 RTT 的问题)。例如: TCP 的修改, RFC1423 (高性能扩展), RFC2018 ( SACK )、 RFC2582 ( New Reno )、 RFC2883 ( D-SACK )、和 RFC2988 ( RTO 计算)都或多或少的提高了点效率,但最根本的 AIMD 算法没有解决。 HS TCP ( RFC 3649 )通过根本上改变 TCP 拥塞控制算法来在高 BDP 网络中获得高带宽利用率,但公平性问题仍然存在。
考虑到上面的背景,需要一种在高 BDP 网络支持高性能数据传输的传输协议。我们推荐一个应用程序级别的传输协议,叫 UDT 或基于 UDP 的数据传输协议并拥有用塞控制算法。
本文描述两个正交的部分, UDP 协议和 UDT 拥塞控制算法。一个应用层级别的协议,位于 UDP 之上,使用其他的拥塞算法,然而这些本文中描述的算法也可以在其他协议中实现,例如: TCP 。
一个协议的参考实现叫 [UDT] ;详细的拥塞控制算法的性能分析在 [GHG04] 中可以找到。
UDT 主要用在小数量的 bulk 源共享富裕带宽的情况下,最典型的例子就是建立在光纤广域网上的网格计算,一些研究所在这样的网络上运行他们的分布式的数据密集程序,例如,远程访问仪器、分布式数据挖掘和高分辨率的多媒体流。
UDT 的主要目标是效率、公平、稳定。单个的或少量的 UDT 流应该利用所有高速连接提供的可用带宽,即使带宽变化的很剧烈。同时,所有并发的流必须公平地共享带宽,不依赖于不同的带宽瓶劲、起始时间、 RTT 。稳定性要求包发送速率应该一直会聚可用带宽非常快,并且必须避免拥塞碰撞。
UDT 并不是在瓶劲带宽相对较小的和大量多元短文件流的情况下用来取代 TCP 的。
UDT 主要作为 TCP 的朋友,和 TCP 并存, UDT 分配的带宽不应该超过根据 MAX-MIN 规则的最大最小公平共享原则。(备注,最大最小规则允许 UDT 在高 BDP 连接下分配 TCP 不能使用的可用带宽)。我们
UDT 是双工的,每个 UDT 实体有两个部分:发送和接收。发送者根据流量控制和速率控制来发送(和重传)应用程序数据。接收者接收数据包和控制包,并根据接收到的包发送控制包。发送和接收程序共享同一个 UDP 端口来发送和接收。
接收者也负责触发和处理所有的控制事件,包括拥塞控制和可靠性控制和他们的相对机制,例如 RTT 估计、带宽估计、应答和重传。
UDT 总是试着将应用层数据打包成固定的大小,除非数据不够这么大。和 TCP 相似的是,这个固定的包大小叫做 MSS (最大包大小)。由于期望 UDT 用来传输大块数据流,我们假定只有很小的一部分不规则的大小的包在 UDT session 中。 MSS 可以通过应用程序来安装, MTU 是其最优值(包括所有包头)。
UDT 拥塞控制算法将速率控制和窗口(流量控制)合并起来,前者调整包的发送周期,后者限制最大的位被应答的包。在速率控制中使用的参数通过带宽估计技术来更新,它继承来自基于接收的包方法。同时,速率控制周期是估计 RTT 的常量,流控制参数依赖于对方的数据到达速度,另外接收端释放的缓冲区的大小。
UDT 有两种包:数据包和控制包。他们通过包头的第一位来区分(标志位)。如果是 0 ,表示是数据包, 1 表示是控制包。
数据包结构如下显示:
0 1 3 4
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
0 |
包序号 |
应用数据 |
包序号是 UDT 数据包头中唯一的内容。它是一个无符号整数,使用标志位后的 31 位, UDT 使用包基础的需要,例如,每个非重传的包都增加序号 1 。序号在到达最大值 2^31-1 的时候覆盖。紧跟在这些数据后面的是应用程序数据。
控制包结构如下:
0 1 3 4
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
1 |
类型 |
保留 |
ACK 序号 |
控制信息字段 |
有 6 种类型的控制包在 UDT 中, bit1-3 表示这些信息。前 32 位在包头中必须存在。控制信息字段包括 0 (例如,它不存在)或者多个 32 位无符号整数,这由包类型决定。
UDT 使用应答子序号的方法。每个 ACK/ACK2 包有一个无符号的 16 位序号,它独立于数据包需要。它使用位 16-31 。应答需要从 0 到( 2^16-1 )。位 16-31 在其他控制包中没有定义。
类型 |
说明 |
控制信息 |
000 |
协议连接握手 |
1 . 32 位 UDT 版本 2 . 32 位 内部顺序号 3 . 32 位 MSS (字节) 4 . 32 位 最大流量窗口大小(字节)
|
001 |
保活 |
没有 |
010 |
应答,位 16-31 是应答序号 |
1 . 32 位包序号,先前接收到的包序号 2 . 32 位, RTT (微秒) 3 . 32 位, RTT 变量或者 RTTVar ( 微秒 ) 4 . 32 位,流量窗口大小(包的数量) 5 . 32 位,连接容量估计(每秒包的数量) |
011 |
Negative 应答( NAK ) |
丢失信息的 32 位整数数组,见 3.9 节 |
100 |
保留 |
这种类型的控制信息保留作为拥塞警告使用,从接收到发送端。一个拥塞警告能被 ECN 或包延迟增加趋势的度量方法触发。 |
101 |
关闭 |
|
110 |
应答一个应答( ACK2 ) |
16-31 位,应答序号。 |
111 |
4-15 的解释 |
保留将来使用 |
注意,对于数据和控制包来说,可以从 UDP 协议头中得到实际的包大小。包大小信息能被用来得到有效的数据负载和 NAK 包中的控制信息字段大小。
UDT 在接收端使用 4 个定时器来触发不同的周期事件,包括速率控制、应答、丢失报告( negative 应答)和重传 / 连接维护。
UDT 中的定时器使用系统时间作为源。 UDT 接收端主动查询系统时间来检查一个定时器是否过期。对于某个定时器 T 来说,其拥有周期 TP ,将定变量 t 用来记录最近 T 被设置或复位的时间。如果 T 在系统时间 t0 ( t= t0 )被复位,那么任何 t1 ( t1-t>=TP )是 T 过期的条件。
四个定时器是: RC 定时器、 ACK 定时器、 NAK 定时器、 EXP 定时器。他们的周期分别是: RCTP 、 ATP 、 NTP 、 ETP 。
RC 定时器用来触发周期性的速率控制。 ACK 定时器用来触发周期性的有选择的应答(应答包)。 RCTP 和 ATP 是常量值,值为: RCTP=ATP=0.01 秒。
NAK 被用来触发 negative 应答( NAK 包)。重传定时器被用来触发一个数据包的重传和维护连接状态。他们周期依赖于对于 RTT 的估计。 ETP 值也依赖于连续 EXP 时间溢出的次数。推荐的 RTT 初始值是 0.1 秒,而 NTP 和 ETP 的初始值是: NTP=3*RTT , ETP=3*RTT+ATP 。
在每次 bounded UDP 接收操作(如果收到一个 UDP 包,一些额外的必须的数据处理时间)时查询系统时间来检查四个定时器是否已经过期。推荐的周期粒度是微秒。 UDP 接收时间溢出值是实现的一个选择,这依赖于循环查询的负担和事件周期精确度之间的权衡。
速率控制事件更新包发送周期, UDT 发送端使用 STP 来安排数据包的发送。假定一个在时间 t0 被发送,那么下一次包发送时间是( t0+ STP )。换句话说,如果前面的包发送花费了 t’ 时间,发送端将等待( STP-t’ )来发送下一个数据包(如果 STP-t’ <0 ,就不需要等待了)。这个等待间隔需要一个高精确度的实现,推荐使用 CPU 时钟周期粒度。
A. SND PKT 历史窗口:一个循环数组记录每个数据包的开始时间
B. 发送端丢失链表:发送段丢失列表是一个连接链表,用来存储被接收方 NAK 包中返回的丢失包序号。这些数字以增加的顺序存储。
A. 如果发送端的丢失链表是非空的,重传第一个在 list 中的包,并删除该成员,到 5 。
B. 等待有应用程序数据需要发送
C. 如果未应答的包数量超过了两量窗口的大小,转到 1 。如果不是包装一个新的包并发送它。
D. 如果当前包的序号是 16n , n 是一个整数,转第 2 步。
E. 在 SND PKT 历史窗口中记录包的发送时间
F. 如果这是自上次发送速率降低之后的第一个包,等外 SYN 时间。
G. 等外( STP – t )时间, t 是第 1 到第 4 步之间的总时间,然后转到 1 。
A. 接收端丢失链表:是一个 duple 连接链表,元素的值包括:丢失数据包的序号、最近丢失包的反馈时间和包已经被反馈的次数。值以包序号增序的方式存储。
B. 应答历史窗口:每个发送 ACK 的和时间一个循环数组;由于其循环的特性,意味着如果数组中没有更多空间的时候新的值将覆盖老的值。
C. RCV PKT 历史窗口:一个用来记录每个包到达时间的循环数组。
D. 对包窗口:一个用来记录每个探测包对之间的时间间隔。
E. LRSN :一个用来记录最大接收数据包需要的变量。 LRSN 被初始化为初始序号减 1 。
A. 查询系统时间来检查 RC 、 ACK 、 NAK 、或 EXP 定时器是否过期。如果任一定时器过期,处理事件(本节下面介绍)并复位过期的定时器。
B. 启动一个时间 bounded UDP 接收。如果每个包到,转 1 。
C. 设置 exp-count 为 1 ,并更新 ETP 为: ETP=RTT+4*RTTVar + ATP 。
D. 如果所有的发送数据包已经被应答,复位 EXP 时间变量。
E. 检查包头的标志位。如果是一个控制包,根据类型处理它,然后转 1 。
F. 如果当前数据包的需要是 16n+1 , n 是一个整数,记录当前包和上个在对包窗口中数据包的时间间隔。
G. 在 PKT 历史窗口中记录包到达时间
H. 如果当前数据包的序号大于 LRSN+1 ,将所有在(但不包括)这两个值之间的序号放入接收丢失链表,并在一个 NAK 包中将这些序号发送给发送端。如果序号小于 LRSN ,从接收丢失链表中删除它。
I. 更新 LRSN ,转 1 。
通过速率控制算法来更新 STP (见 3.6 节)。
过程如下:
A. 按照下面的原则查找接收端所接收到的所有包之前的序号:如果接收者丢失链表是空的, ACK 号码是 LRSN+1 ,否则是在接收丢失队列中的最小序号。
B. 如果应答号不大于曾经被 ACK2 应答的最大应答号,或等于上次应答的应答号并且两次应答之间的时间间隔小于 RTT+4*RTTVar ,停止(不发送应答)。
C. 分配这个应答一个唯一增加的 ACK 序列号,推荐采用 ACK 序列号按步骤 1 增加,并且重叠在达到最大值之后。
D. 根据下面的算法来计算包的抵达速度:使用 PKT 历史窗口中的值计算最近 16 个包抵达间隔( AI )中值。在这 16 个值中,删除那些大于 AI*8 或小于 AI*8 的包,如果最后剩余 8 个值,计算他们的平均值 (AI’) ,包抵达速度是 1/AI’ (每秒包的数量),否则是 0 。
E. 根据 3.7 节中的内容为每端( W )计算流量窗口。然后计算有效的流量窗口大小为:最大( W ,可用接收方缓冲大小), 2 )。
F. 根据下面的算法来计算连接容量估计。如果流量控制快启动阶段( 3.7 )一直继续,返回 0 ,否则计算最近 16 个对包间隔( PI ),这些值在对包窗口中,那么连接容量就是 1/PI (每秒包的数量)。
G. 打包应答序列号,应答号, RTT , RTT 变量,有效的流量窗口大小并估计连接,将他们放入 ACK 包中,然后发送出去。
H. 记录 ACK 序列号,应答号和这个应答的开始时间,并放入历史窗口中。
Ø 查找接受方的丢失链表,找到所有上次反馈时间是( k* ( RTT+4*RTTVar ) )前的包, k 当前这个包的反馈次数加 1 ,如果没有反馈丢失,停止。
Ø 压缩第一步中得到的序号(见 3.9 ),然后在一个 NAK 包中发送他们到发送方。
Ø 如果不是停止流量控制快启动阶段。
A. 如果发送端的丢失链表不是空的,停止
B. 将所有未应答的包放到发送端的丢失链表中
C. 如果 (exp-count>16) 并且自上次从对方接收到一个包以来的总时间超过 3 秒,或者这个时间已经超过 3 分钟了,这被认为是连接已经断开,关闭 UDT 连接。
D. 如果没有数据,也就没有应答,发送一个保活包给对端,否则将所有未应答包的序号放入发送丢失列表中。
E. 更新 exp-count 为: exp-count= exp-count+1
F. 更新 ETP 为: ETP=exp-count* ( RTT+4*RTTVar ) +ATP 。
A. 更新最大的应答序号
B. 更新 RTT 和 RTTVar 为: RTT = rtt , RTTVar = rv ; rtt 和 rv 是 ACK 包中的 RTT 和 RTTVar 值。
C. 更新 NTP 和 ETP 为: NTP=RTT+4*RTTVar ; ETP=exp-count* ( RTT+4*RTTVar ) +ATP 。
D. 更新连接容量估计: B= ( B*7+b ) /8 , b 是 ACK 包带的值。
E. 更新流量窗口大小为 ACK 中的值。
F. 发送 ACK2 包,并设置与 ACK 序号相同的应答号到对端
G. 复位 EXP 定时器
A. 将所有 NAK 包中带的序号放入发送方的丢失列表中
B. 通过速率控制来更新 STP (见 3.6 )
C. 复位 EXP 定时器
Ø 在 ACK 历史窗口中根据接收到的 ACK2 序列号查找行营的 ACK 包。
Ø 更新曾经被应答的最大应答号
Ø 根据 ACK2 的到达时间和 ACK 离开时间计算新的 rtt 值,并且更新 RTT 和 RTTVar 值为:
RTTVar = (RTTVar *3 +abs(rtt-RTT)/4
RTT = (RTT *7+rtt)/8
RTT 和 RTTVar 的初始值是 0.1 秒和 0.05 秒。
Ø 更新 NTP 和 ETP 为:
NTP = RTT ;
ETP = (exp-count +1)* RTT+ATP
什么也不做
见 3.8 节
STP 被初始为最小的时间精度( 1 个 CPU 周期或 1 毫秒)。这是在快启动阶段,一般收到一个 ACK 包其携带的估计带宽大于 0 这个阶段就停止了。包的发送周期被设置为 1/W , W 是 ACK 携带的流量窗口的大小。
快启动阶段仅仅在开始一个 UDT 连接的时候发生,且不会在 UDT 连接的以后再出现。在快启动阶段之后,下面的算法就要工作了。
1. 如果在上一个 RCTP 时间内,没有收到一个 ACK ,停止
2. 计算在上个 RCTP 时间内的丢失率,计算方法是根据总共发送的包与 NAK 反馈中总共丢失包的数量。如果丢失率大于 0.1% ,停止。
3. 下个 RCTP 时间内发送包的增加数量如下计算: (inc)
If (B<=C) inc = 1/MSS
Else inc = max (10^(ceil(log10((B-C)*MSS*8)))*Beta/MSS,1/MSS)
B 是连接容量估计, C 是当前的发送速度。两个都计算为每秒多少个包。 MSS 是以字节计算的; Beta 是值为 0.0000015 的常量。
4. 更新 STP : STP= ( STP*RCTP ) / ( STP*inc + RCTP )
5. 计算真正的数据发送周期( rsp ),从 SND PKT 历史窗口中得到,如果( STP<0.5 *rsp )设置 STP 为( 0.5 * rsp )。
6. 如果( STP<1.0 ),设置 STP 为 1.0 。
1. LSD :自上次速率降低后发送的最大序号
2. NumNAK :自上次 LSD 更新以后的 NAK 数量
3. AvgNAK :当最大序号大于 LSD 时两次事件之间的 NAK 移动的平均数。
4. DR :在 1 到 AvgNAK 之间的随机平均数。
1. 如果 NAK 中最大的丢失序列号大于 LSD :
增加 STP 为: STP=STP* ( 1+1/8 )
更新 AvgNAK 为: AvgNAK = ( AvgNAK *7 +NumNAK ) /8
更新 DR
复位 NumNAK = 0
记录 LSD
2. 否则,增加 NumNAK 按照 1 个步骤增加;如果 NumNAK % DR = 0 ;增加 STP 为: STP=STP* ( 1+1/8 );记录 LSD 。
流量控制窗口大小( W )初始值是 16 。
1. 流量控制快启动:如果没有 NAK 产生或者 W 没有到达或超过 15 个包,并且 AS>0 ,流量窗口大小更新为应答包的总数量。
2. 否则,如果( AS>0 ), W 更新为:( AS 是包的到达速度)
W= ceil (W *0.875+AS* (RTT +ATP) *0.125)
3. 限制 W 到对方最大流量窗口大小。
一个 UDT 实体首先作为一个 SERVER 启动,当一个客户端需要连接的时候其发送握手包。客户端在从服务端接收到一个握手响应包或时间溢出之前,应该每隔一段时间发送一个握手包(时间间隔由响应时间和系统 overhead 来权衡)。
握手包有如下信息:
1. UDT 版本:这个值是兼容的目的。当前的版本是 2
2. 初始序号:这是发送这个 UDT 实体将来用于发送数据包的起始序号。它必须是一个在 1 到( 2^31-1 )之间的随机值。另外,建议这个值在合理的时间历史窗口中不应该重复。
3. MSS :数据包的大小(通过 IP 有效负载来度量)
4. 最大的流量窗口大小:这是接收到握手信息的 UDT 实体允许的最大流量窗口大小,窗口大小通常限制为接收端的数据结构大小。
服务器接收到一个握手包之后,比较 MSS 值和他自己的值并设置它自己的值为较小的值。结果值也在握手响应中被发送到客户端,另外还有服务器的版本信息,初始序列号,最大流量窗口大小。
版本字段用来检查两端的兼容性。初始序列号和最大流量窗口大小用于初始化接收到这个握手包的 UDT 实体参数。
服务器在第一步完成以后就准备发送或接收数据。然而,只要从同一个客户端接收任何握手包,其应该发送响应包。
客户端一旦得到服务器的一个握手响应其就进入发送和接收数据状态。设置它自己的 MSS 为握手响应包中的值并初始化相应的参数为包中的值(序列号、最大流量窗口)。如果收到任何其他的握手信息,丢掉它。
如果其中的 UDT 实体要关闭,它将发送一个关闭信息到对端;对方收到这个信息以后将自己关闭。这个关闭信息通过 UDP 传输,仅仅发送一次,并不保证一定收到。如果消息没有收到,对方将根据时间溢出机制来关闭连接。
NAK 包中携带的丢失信息是一个 32-bit 整数的数组。如果数组的中数字是一个正常的序号(第 1 位是 0 ),这意味着这个序号的包丢失了,如果第 1 位是 1 ,意味着从这个号码开始(包括该号码)到下一个数组中的元素(包括这个元素值)之间的包(它的第 1 位必须是 0 )都丢失。
例如,下面的 NAK 中携带的信息:
0x00000002, 0x80000006, 0x0000000B, 0x0000000E
上面的信息表明序号为: 2 , 6 , 7 , 8 , 9 , 10 , 11 , 14 的包都丢了。
UDT 能够充分利用当前有线网络的独立于连接容量的可用带宽 、 RTT 、后台共存流、给定的连接比特错误率。 UDT 在没有数据包丢失的情况下从 0bits/s 到 90% 带宽需要一个常量时间,这个时间是 7.5 秒。 UDT 并不适合无线网络。
UDT 的确满足单瓶劲网络拓扑的最大 - 最小公平性。在多个瓶劲情况下,根据最大最小原则它能保证较小瓶劲连接或者至少一半的平等共享 (it guarantees that flows over smaller bottleneck links obtain at least half of their fair share according to max-min rule ) 。 RTT 对公平性都一点影响。
当和大块的 TCP 流共存的时候, TCP 能占用比 UDT 更多的带宽,除了三种情况:
1. 网络 BDP 非常大, TCP 不能利用他们的公平共享带宽。这种情况下, UDT 将占用 TCP 不能利用的带宽。
2. 连接容量是如此的小,从而导致 UDT 的带宽估计技术不能最有的工作;模拟显示这个极限连接容量大约是 100kb/s 。
3. 在使用 FIFO 队列作为网络路径的网络中,如果队列大小大于 BDP , TCP 的共享带宽随着队列大小的增加而降低。然而,抵达 UDT 的共享带宽是,队列大小通常超过实际路由器 / 交换机提供的数量。
当短( timewise )类似 web 的 TCP 流和小的并发 UDT 流共存的时候, UDT 在 TCP 流上的效果非常小。
更多的分析在 [GHG03] 。
UDT 并没有使用特定的安全机制,相反,它依赖于应用程序提供的授权和底层提供的安全机制。
然而,由于 UDP 是无连接的, UDT 实现应该检查所有达到的包是否是预期的来源。这是从 socket 的 API 连接概念中继承而来,其连接只是接收指定来源的数据。
http://sourceforge.net/projects/udt/
http://udt.sourceforge.net/software.html