这是一个对于uTorrent建立在UDP之上的传输层协议。可以和目的就是为了改善TCP的一些特性,可以和TCP作一些对比学习。
所以我想专门对这个拓展,记录一章。
BEP:uTorrent transport protocol
作用:在DSL 和 cable modems中,会有一个send buffer由于TCP connection采用的是平均分配带宽的方式。
在所有的服务中,BT会建立很多的TCP connections,而其他本来应该具有高优先级的服务比如:电子邮件,电话,网页浏览。
它们因为具有的TCP connection少,所以就会争抢到少量带宽,从而经历大量延迟。
而采用架设在UDP层之上的uTP协议,能够根据网络的延迟来很好滴调控自身,使没有其他服务的时候充分利用带宽,而有其他服务的时候让出带宽。
其实迅雷的网页模式和下载模式大概就是这个意思,只是迅雷给user一个使用的选择吧,当然实际效果怎么样,主要还是看自己的网啦。
格式:既然是建立在UDP之上一层,帧结构就应该是这样。<uTP帧>=<帧头><payload>,其中<payload>=<UDP帧>。或者还有个校验什么的。
标准中主要给出了帧头信息,如下
version 1 header:
0 4 8 16 24 32
+-------+-------+---------------+---------------+---------------+
| type | ver | extension | connection_id |
+-------+-------+---------------+---------------+---------------+
| timestamp_microseconds |
+---------------+---------------+---------------+---------------+
| timestamp_difference_microseconds |
+---------------+---------------+---------------+---------------+
| wnd_size |
+---------------+---------------+---------------+---------------+
| seq_nr | ack_nr |
+---------------+---------------+---------------+---------------+
type:帧类型。一共有ST_DATA = 0,ST_FIN = 1,ST_STATE = 2,ST_RESET = 3,ST_SYN = 4 五种。
分别表示数据帧,断开连接帧(TCP 的FIN),确认帧(TCP 的ACK),重建连接帧,建立连接帧(TCP 的SYN)
()表示,对应TCP里面的类型。
ver:版本号。不重要。
extension:扩展字段,比如selective ack就需要这个字段非零,没有扩展置零即可。
connection id:用来唯一表示一个连接的,一个connection会使用两个id,一个用来发,一个用来收,注意本端的收=对端的发,所以一个connection有两个id,而变量一共有四个:一端的收发两个,另一端的收发两个。而一端的收发id只相差1(连接发起一端随机产生一个数,作为接收id,此端发送id=接收id+1),所以可以用这个id来唯一标识一个connection。
timestamp_microseconds:表示此帧的发送时间。
timestamp_difference_microseconds:单向传播时延,用帧的收到时间-timestamp_microseconds的到。由于两端的时间并没有同步,或者说并没有到达微秒级别的同步,这个字段的绝对值并没有实际意义,但是对比两次timestamp_difference_microseconds的差别具有意义。
wnd_size:窗口大小,这个和TCP里面的意义一样,是对端给的一个限制发送的标志,用于流量控制。
seq_nr:包序号。这个和TCP一样,双方各自维护一个序列来表示自己的发送。
ack_nr:确认号。注意这里和TCP不同,TCP是应答下一个期望得到的对方的seq。而这里是收到了哪个seq就ack那个seq。
比如,TCP中,我收到了对方的seq1000我的ack就是1001.而在这里我收到了seq1000我还是ack1000。
帧头信息总结,在简单的交互之中,最重要的就是这四个参数:type,connection id,seq_nr,ack_nr。在接下来的connection setup流程中可以看到。
而timestamp那两个字段要具体的网络环境才能确定。
最后还有个本端需要知道的参数是max_window,用于拥塞控制,相关计算后面会有。
connection setup
Here is a diagram illustrating the exchanges and states to initiate a connection. The c.* refers to a state in the socket itself, pkt.* refers to a field in the packet header.
initiating endpoint accepting endpoint
| c.state = CS_SYN_SENT |
| c.seq_nr = 1 |
| c.conn_id_recv = rand() |
| c.conn_id_send = c.conn_id_recv + 1 |
| |
| |
| ST_SYN |
| seq_nr=c.seq_nr++ |
| ack_nr=* |
| conn_id=c.rcv_conn_id |
| >-------------------------------------------> |
| c.receive_conn_id = pkt.conn_id+1 |
| c.send_conn_id = pkt.conn_id |
| c.seq_nr = rand() |
| c.ack_nr = pkt.seq_nr |
| c.state = CS_CONNECTED |
| |
| |
| |
| |
| ST_STATE |
| seq_nr=c.seq_nr++ |
| ack_nr=c.ack_nr |
| conn_id=c.send_conn_id |
| <------------------------------------------< |
| c.state = CS_CONNECTED |
| c.ack_nr = pkt.seq_nr |
| |
| | connection established
.. ..|.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..|.. ..
| |
| |
| ST_DATA |
| seq_nr=c.seq_nr++ |
| ack_nr=c.ack_nr |
| conn_id=c.conn_id_send |
| >-------------------------------------------> |
| c.ack_nr = pkt.seq_nr |
| |
| |
| |
| ST_DATA |
| seq_nr=c.seq_nr++ |
| ack_nr=c.ack_nr |
| conn_id=c.send_conn_id |
| <------------------------------------------< |
| c.ack_nr = pkt.seq_nr |
| |
| |
V V
Congestion Control
前面已经提到了,流量控制使用参数:wnd_size 而拥塞控制使用参数:max_window。
其实拥塞控制的所有内容都是按照max_window这个参数展开的。
注意:max_window和wnd_size的单位都是bytes,current_window的单位也是bytes。
那么下面分两种情况讨论max_window的变化:(1)正常情况基于delay变化。(2)有丢包重传发生。
(1)正常情况下基于delay变化
先要介绍几个delay。
timestamp:发送时的时间。
timestamp_variance:收到时间-发送时间、即单边延迟。
base_delay:2min 内最小的timestamp_variance。
our_delay:本次timestamp_variance-base_delay。
off_target:CCONTROL_TARGET-our_delay,其中CCONTROL_TARGET=100ms。
delay_factor = off_target / CCONTROL_TARGET;
window_factor = outstanding_packet / max_window;
scaled_gain = MAX_CWND_INCREASE_PACKETS_PER_RTT * delay_factor * window_factor
max_window += scaled_gain
即可计算max_window
(2)有丢包发生。
①数据驱动的重传。
即越界应答超过3个以上,或者3个以上的duplicate ACKs。
max_window=max_window/2
②时间驱动,即timeout。
它会有各种计算RTT的公式,然后
rtt and rtt_var are calculated by the following formula, every time a packet is ACKed:
delta = rtt - packet_rtt
rtt_var += (abs(delta) - rtt_var) / 4;
rtt += (packet_rtt - rtt) / 8;
The default timeout for packets associated with the socket is also updated every time rtt and rtt_var is updated. It is set to:
timeout = max(rtt + rtt_var * 4, 500);
一旦超时以后,就会
max_window=max_window/2
包大小也会设置为最小的150bytes。