uTorrent transport protocol

本文介绍了uTorrent传输协议(uTP)的设计背景、工作原理及关键技术。uTP旨在优化BitTorrent客户端在网络上的表现,通过避免中断互联网连接的同时充分利用未使用的带宽。
部署运行你感兴趣的模型镜像

设计人员

uTorrent传输协议由Ludvig Strigeus,Greg Hazel,Stanislav Shalunov,Arvid Norberg和Bram Cohen设计。

产生背景

uTP的起源动机是让BitTorrent客户端不会中断互联网连接,同时仍然充分利用未使用的带宽。

问题是DSL和电缆调制解调器通常具有与其最大发送速率不成比例的发送缓冲器,其可以容纳几秒钟的数据包。BitTorrent流量通常是后台传输,应该比检查电子邮件,电话和浏览网页的优先级低,但是当使用常规TCP连接时,BitTorrent会快速填满发送缓冲区(多并发连接),为所有交互式流量增加多秒延迟。

BitTorrent使用多个TCP连接这一事实使其在与其他服务竞争带宽时具有不公平的优势,这夸大了BitTorrent填充上传管道的影响。这是因为TCP在连接中均匀分配可用带宽,并且一个应用程序使用的连接越多,它所获得的带宽份额就越大。

这个问题的传统解决方案是将BitTorrent客户端的上传速率限制在上行链路容量的80%。80%为交互式交通留下了一些空间。

该解决方案的主要缺点是:

  1. 用户需要配置他/她的BitTorrent客户端,它不会开箱即用。
  2. 用户需要知道他/她的互联网连接的上传容量。此容量可能会发生变化,尤其是在可能连接到大量不同网络的笔记本电脑上。
  3. 20%的净空是任意的,浪费带宽。每当没有交互式流量与BitTorrent竞争时,额外的20%就会被浪费掉。每当存在竞争性交互流量时,它就不能使用超过20%的容量。

uTP通过使用调制解调器队列大小作为其发送速率的控制器来解决此问题。当队列变得太大时,它会限制回来。

这使得它可以在没有竞争的情况下利用完整的上传容量,并且当有大量交互流量时,它可以让它减少到几乎没有。

总体介绍

本文档假设您了解TCP和基于窗口的拥塞控制的工作原理。

uTP是一种在UDP之上分层的传输协议。因此,它必须(并且有能力)实现自己的拥塞控制。

与TCP相比的主要区别是基于延迟的拥塞控制。请参阅拥塞控制部分。

与TCP一样,uTP使用基于窗口的拥塞控制。每个socket具有一个max_window,其决定在任何给定的时间,socket可以具有处于 in-flight 状态的最大字节数。已发送但尚未确认的任何数据包都被视为处于 in-flight 状态,处于 in-flight 状态的字节数被定义为 cur_window。

只有在 cur_window + packet_size <= min(max_window,wnd_size),socket 允许发送数据包。数据包大小可能会有所不同,请参阅数据包大小部分。wnd_size是对端的建议窗口。它设定了对端处于 in-flight 状态的数据包数量上限。

如果 max_window 小于数据包大小,则实现可能违反上述规则,并且它会调度数据包以使平均 cur_window <= max_window。

每个socket记录来自对端的最后一次延迟测量的状态(reply_micro)。无论何时收到数据包,都会通过从主机当前时间减去timestamp_microseconds字段值(参阅头部格式部分)来更新此状态(以微秒为单位)。每次发送数据包时,socket 的reply_micro值都会放入数据包标头的timestamp_difference_microseconds字段中。

与TCP不同,uTP中的序列号和ACK指的是数据包,而不是字节。这意味着uTP 在重新发送时无法重新打包数据。

每个socket会记录发送数据包时要使用的下一个序列号seq_nr。它还记录上次接收的序列号ack_nr。最旧的未确认的数据包是seq_nr - cur_window。

头部格式

version 1 头部:
在这里插入图片描述
所有字段都按网络字节顺序(大端)。

type

type字段描述了数据包的类型。它可以是以下之一:

  • ST_DATA = 0
    常规数据包。套接字处于连接状态并具有要发送的数据。ST_DATA数据包始终具有数据有效负载。

  • ST_FIN = 1
    终止连接。这是最后一个数据包。类似于TCP FIN标志,关闭连接。属于该连接的数据包,序列号永远不会有大于此数据包中的序列号。socket将此序列号记录为eof_pkt。即使在收到ST_FIN数据包后,socket仍会等待一段时间,接收那些可能是丢失的或乱序到达的数据包。

  • ST_STATE = 2
    状态包。用于传输没有数据的ACK。不包含任何有效负载的数据包不会增加seq_nr。

  • ST_RESET = 3
    强行终止连接。与TCP RST标志类似。远程主机没有此连接的任何状态,标识它是陈旧的,应该被终止。

  • ST_SYN = 4
    连接SYN。与TCP SYN标志类似,此数据包启动连接。序列号初始化为1。connection ID 初始化为随机数。syn数据包是特殊的,在此连接上发送的所有后续数据包(除了重新发送的ST_SYN)都使用(connection ID + 1)发送。connection ID 是另一端在其响应中使用的。

    当接收到ST_SYN时,应使用包头中的ID初始化新socket。应将socket的发送ID初始化为ID + 1。返回通道的序列号初始化为随机数。另一端期望收到响应ST_STATE包(仅ACK)。

version

协议版本。目前的版本是1。

connection_id

这是一个随机的唯一编号,用于标识属于同一连接的所有数据包。每个socket都有一个用于发送数据包的连接ID和用于接收数据包的不同连接ID。启动连接的端点决定使用哪个ID,返回路径具有相同的ID + 1。

timestamp_microseconds

这是发送此数据包的时间戳的“微秒”部分。这是在posix上使用gettimeofday()和在windows上使用QueryPerformanceTimer()设置的。时间戳精度越高越好,时间戳设定越接近实际传输时间越好。

timestamp_difference_microseconds

这是收到最新的一个数据包时,数据包的时间戳和本地时间之间的差值。这是从远端到本地的链路最新单向延迟测量。

当新打开socekt并且还没有任何延迟样本时,必须将其设置为0。

wnd_size

通知接收窗口。这是32位宽,以字节为单位指定。

window size是指当前正在传输的字节数,即已发送但未被确认ACK。当接收缓冲区即将被填满时,如果它不能更快​​地接收,则通知接收窗口可以让另一端修改窗口大小。

发送数据包时,应将其设置为socket接收缓冲区中剩余的字节数。

seq_nr

这是该数据包的序列号。与TCP相反,uTP序列号不是指字节,而是指数据包。序列号告诉另一端应该将数据包送回应用层的顺序。

ack_nr

这是数据包的发送者最后在对端接收的序列号。

extension

扩展链表中第一个扩展的类型。0表示没有扩展名。

目前有一个扩展:

  1. Selective acks

扩展是链接形式的,就像TCP options一样。如果扩展字段非零,则紧跟在uTP头之后是两个字节:
在这里插入图片描述
其中extension指定链表中下一个扩展的类型,0终止列表。len指定这个扩展的字节数。只需向前len个字节就可以跳过未知的扩展名。

SELECTIVE ACK

Selective ACK是一种非顺序的、可选择性的ACK包扩展。其有效载荷是至少32位的位掩码,长度是32位的倍数。每个位代表发送窗口中的一个数据包。发送窗口之外的位将被忽略。设置位(1)指定已接收数据包,清除位(0)指定尚未接收数据包。头部格式如下:
在这里插入图片描述

请注意,扩展名的len字段指的是字节,在此扩展名中必须至少为4,并且为4的倍数。

仅当在接收的流中跳过至少一个序列号时才发送选择性ACK。因此,掩码中的第一个比特表示ack_nr + 2。当发送该包时,则ack_nr + 1被假设为已被丢弃或丢失。设置位表示已接收的数据包,清除位表示尚未接收的数据包。

位掩码具有反向字节顺序。第一个字节以相反的顺序表示包[ack_nr + 2,ack_nr + 2 + 7]。字节中的最低有效位表示ack_nr + 2,字节中的最高有效位表示ack_nr + 2 + 7。掩码中的下一个字节以相反的顺序表示[ack_nr + 2 + 8,ack_nr + 2 + 15],等等。位掩码不限于32位,而是可以是任何大小。

以下是位掩码的布局,表示在Selective ACK位域中表示的前32个包的acks:
在这里插入图片描述
图中的数字将位掩码中的位映射到偏移量,然后加上 ack_nr来计算该位要确认的序列号。

建立连接

下图说明了启动连接的通信和状态。c.*指的是socket本身的状态,pkt.*指的是包头中的字段。
在这里插入图片描述
连接由conn_id标头标识。如果新连接的连接ID与现有连接冲突,则连接尝试将失败,因为ST_SYN数据包在现有流中将是异常的,并被忽略。

包丢失

如果序列号(seq_nr - cur_window)的数据包尚未被确认(这是发送缓冲区中最早的数据包,也是需要被确认的下一个数据包),但跳过该数据包之后的3个或更多数据包已被确认(通过Selective ACK),则假设该数据包丢失了。类似地,当接收到3个重复的ack时,假设ack_nr + 1已经丢失(如果已经发送了具有该序列号的数据包)。

这也适用于Selective ACK。在选择性确认消息中被确认的每个包被计为一个重复的ack,如果它>=3,则在其之后的具有>=3个被确认的数据包都应该被重新发送。

当数据包丢失时,max_window乘以0.5以模拟TCP。

超时

通过落入范围(last_ack_nr,ack_nr] 或明确地被Selective ACK消息被确认的每个数据包都应该用于更新rtt(往返时间)和rtt_var(rtt方差)测量。这里的 last_ack_nr 是 socket 在当前数据包之前收到的最后一个ack_nr,ack_nr是当前数据包中的字段。

该 rrt 和 rtt_var 只更新已发送且只发送了一次的数据包。这避免了需要知道是哪个数据包被确认的问题,第一个或第二个数据包。

每次确认数据包时,rtt 和 rtt_var 都由以下公式计算:

delta = rtt - packet_rtt
rtt_var +=(abs(delta) - rtt_var) / 4;
rtt += (packet_rtt - rtt) / 8;

每次更新 rtt 和 rtt_var 时,也会更新与socket关联的数据包的默认超时 。它被设置为:

timeout = max(rtt + rtt_var * 4, 500);

其中timeout以毫秒为单位指定。即数据包的最小超时为1/2秒。

每次socket发送或接收数据包时,它都会更新其timeout计数器。如果从上一次timeout计数器重置开始,在timeout毫秒内没有数据包到达,则socket触发超时。它将 packet_size 和 max_window 设置为最小的数据包大小(150字节)。这允许它再发送一个数据包,这就是如果窗口大小降到零,套接字再次启动的方式。

初始超时设置为1000毫秒,稍后根据上面的公式进行更新。对于超时的数据包以及后续的每个数据包,超时加倍。

包大小

为了尽可能减少对慢速拥塞链路的影响,uTP将其数据包大小调整为每个数据包150个字节。使用较小的数据包具有不会阻塞慢速上行链路的优点,具有较长的序列化延迟。使用小数据包的成本使得数据包报头的开销变得很大。在高速率下,使用大的包大小,慢速率下使用小的包大小。

拥塞控制

uTP拥塞控制的总体目标是使用单向缓冲延迟作为主要拥塞测量,丢包也是如此,和TCP类似。关键是要避免在发送数据时使用全部的发送缓冲区运行。这对于DSL / Cable调制解调器来说尤其是一个问题,其中调制解调器中的发送缓冲器通常具有多秒数据的空间。uTP(或任何后台流量协议)的理想缓冲区利用率是以0字节缓冲区利用率运行。即任何其他流量可以在任何时间发送而不受阻碍。实际上,uTP目标延迟设置为100 ms。每个socket的目的是永远不会在发送链路上看到超过100毫秒的延迟,如果有,它会减速。这有效地使uTP流量屈服于任何TCP流量。

这是通过在uTP发送的每个数据包中包含高精度时间戳来实现的,并且接收端计算其自己的高精度计时器与其接收的数据包中的时间戳之间的差异。然后将该差异反馈给包的原始发送方(timestamp_difference_microseconds)。该值作为绝对值没有意义。机器中的时钟很可能不同步,尤其是不低于微秒分辨率,并且数据包在传输的时间也包含在这些时间戳的差异中。但是,与以前的timestamp_difference_microseconds值相比,该值很有用。

每个socket记录在最后两分钟内的滑动最小值。此值称为base_delay,用作基线,即主机之间的最小延迟。当从每个数据包中的时间戳差异中减去base_delay时,您将获得socket上当前缓冲延迟的度量。此测量称为our_delay。它有很多噪音,但用作驱动程序来确定是增加还是减少发送窗口(控制发送速率)。

CCONTROL_TARGET是UTP接受上行链路上的缓冲延迟。目前,延迟目标设置为100毫秒。off_target是实际测量延迟距目标延迟的距离(从CCONTROL_TARGET - our_delay计算得出)。

socket结构中的窗口大小指定了连接上我们可能在in_flight中(未确认)的字节数。发送速率与此窗口大小直接相关。飞行中的字节越多,发送速率越快。在代码中,窗口大小称为 max_window。它的大小大致由以下表达式控制:

delay_factor = off_target / CCONTROL_TARGET;
window_factor = outstanding_packet / max_window;
scaled_gain = MAX_CWND_INCREASE_PACKETS_PER_RTT * delay_factor * window_factor;

第一个因子将off_target缩放到目标延迟的单位。

然后将scaled_gain添加到max_window:

max_window += scaled_gain;

如果off_target大于0,这将使窗口变小,如果off target小于0,则增大窗口。

如果max_window小于0,则将其设置为0。窗口大小为零意味着socket可能不发送任何数据包。在此状态下,socekt将触发超时并将窗口大小强制为一个数据包大小,并发送一个数据包。有关更多信息,请参阅有关超时的部分。

您可能感兴趣的与本文相关的镜像

GPT-SoVITS

GPT-SoVITS

AI应用

GPT-SoVITS 是一个开源的文本到语音(TTS)和语音转换模型,它结合了 GPT 的生成能力和 SoVITS 的语音转换技术。该项目以其强大的声音克隆能力而闻名,仅需少量语音样本(如5秒)即可实现高质量的即时语音合成,也可通过更长的音频(如1分钟)进行微调以获得更逼真的效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值