Tcp-like
之前看了TCP-like的RFC,把大致内容做个记录吧!
一、 简介
Tcp-like(CCID2)是tcp拥塞控制机制的变体,适用于能够适应TCP加性增加乘性减少(AIMD)拥塞控制的拥塞窗口突变的发送者,以及对于希望在快速变化的环境中利用可用带宽的发送者特别有用。
Tcp-like区别于tfrc的地方在于tcp-like倾向于在尽可能短的时间发尽可能多的包(这样可能就会有发送速率的突变),而tfrc则适用于那些希望将发送速率的突变最小化的流。
Tcp-like部分依赖于现有的TCP文档,其流量动态与TCP非常相似,CCID 2的拥塞控制机制是基于SACK的TCP,但不同之处在于:
① 将拥塞控制应用于ack上
② 在TCP中以字节为单位指定的几个参数(如拥塞窗口cwnd)在DCCP中以包为单位
③ 由于DCCP是不可靠协议,因此从不重新传输包,因此拥塞控制机制(将重新传输与新包区分开)针对DCCP上下文重新设计
二、 半连接的例子
-
发送方:
发送DCCP-Data数据包,其中发送的数据包数量由拥塞窗口cwnd控制(类似TCP)。 每个dccp - data数据包使用一个序列号。
发送方还发送一个Ack Ratio特性选项,该选项指定来自接收方的一个Ack包所涵盖的数据包数量(以一种大致适合tcp的方式控制确认率);Ack Ratio默认值为2(一个ack响应两个数据包)。
DCCP头的CCVal字段设置为0。并假设半连接有显式拥塞通知(ECN) 。 -
接收方:
发送一个DCCP-Ack包,确认发送方发送的每个Ack Ratio数据包。
每个DCCP-Ack包使用一个序列号并包含一个Ack向量。DCCP-Ack包中确认的序列号为接收到的序列号最高的包的序列号。
接收方通过Ack向量选项返回接收到的ECN Nonces的总和,允许发送方在概率上验证接收方的行为是否正常。 -
发送方:
继续发送由拥塞窗口控制的DCCP-Data数据包。接收到DCCP-Ack包后,发送方检查其Ack向量以了解标记或丢弃的数据包,并相应地调整其拥塞窗口。因为这是不可靠的传输,所以发送方不会重新传输丢失的数据包 -
发送方:
DCCP-Ack包使用序列号,所以发送方有一些关于丢失或标记的DCCP-Ack包的信息。发送方通过修改发送给接收方的Ack Ratio来响应丢失或标记的DCCP-Ack。 -
发送方:
在每个拥塞窗口至少确认接收方的确认一次。如果两个半连接都处于活动状态,则发送方对接收方确认的确认包括在发送方对接收方数据包的确认中。如果反向路径半连接是静态的,发送方在每个拥塞窗口至少发送一个DCCP-DataAck包。 -
发送方:
估计往返时间(像TCP那样跟踪确认往返时间,或者通过显式的时间戳选项),并计算超时值。
三、 连接建立
在CCID 2半连接上必须使用Ack向量,因此发送方必须向接收方发送“Change R(Send Ack Vector, 1)”选项作为建立连接的一部分。
发送方在从接收方收到相应的“Confirm L(Send Ack Vector, 1)”之前不应该发送数据,但它可以通过DCCPRequest包发送数据。
四、 数据包的拥塞控制
- 发送方维护三个在数据包中测量的整数参数:
cwnd、ssthresh、pipe(发送方对网络中未完成的数据包数量的估计),三个参数的初始值都是由基于SACK的TCP的行为所决定。 - Pipe减小的三种情况:
在确认接收端收到数据包、丢包(相当于TCP的重复确认,NUMDUPACK参数被设置为3时,就发生该情况)、发送超时(像TCP超时重传,但由于DCCP不重传数据,DCCP不需要TCP建议的最小超时一秒,另外超时时将pipe置0)的时候,发送方会减少pipe值。 - 对2中减少pipe的补充:
发送方不得在每个数据包中减少管道一次以上
① 发送方在收到先前推断为丢失的包的确认信息时,也不能再次减量管道。
② 此外,发送方不能减少非数据包(如DCCP-Acks)的管道 - 拥塞事件的处理:
当在一个RTT估计值内发送数据包时,发送方可以将两个丢失或标记视为一个拥塞事件的一部分。
在收到ECN标记(Ack Vector State 1)或通过“重复确认”推断拥塞。
① 在一个超时(timeout)之后,将cwnd减半(四舍五入),ssthresh=new cwnd(cwmd≥1,ssthresh≥2)。
② 当cwnd < ssthresh,意味着发送方处于慢启动状态时:
每两个带有Ack向量状态0(非ecn标记)的新确认的数据包,拥塞窗口增加一个数据包,直到每个确认有Ack比率/2个数据包。
③ 当cwnd >= ssthresh时,在没有丢失或标记包的情况下:
每确认的一个数据窗口,拥塞窗口增加一个包。(初始化时cwmd最大为4个包,ssthresh为任意大)
发送方在发送由单个ack包释放的多个数据包时,可以使用一种基于速率的步调,而不是在单个突发中发送所有释放的数据包。(不明白,不是每两个带有非ECN标记的ack才会释放一个数据包吗?单个怎么释放多个数据包) - 空闲和应用程序受限时的决策
空闲(idle,无数据传输):[RFC2581]应在空闲时期之后慢启动。[RFC2861] 在发送方保持空闲的往返时间内,拥塞窗口减半。
应用程序受限(application-limited,发送速率小于cwnd):[RFC2861] TCP的拥塞窗口不会在应用程序有限的时间段内增加(否则按照之前的算法,不拥塞cwnd一直增加) - 数据丢失和接收慢时的决策
数据丢失:对于每个新确认为“Drop code 2”的包,拥塞窗口“cwnd”减少一个
并且,每当发送方收到一个相关的数据丢失或慢接收器选项,它必须退出慢启动 - 数据包大小问题:
CCID 2不适用于需要在两个包之间有一个固定的时间间隔,并且根据拥塞情况改变包大小而不是包速率的应用程序。CCID2在包中维护一个拥塞窗口,并不会因为包大小的减少而增加拥塞窗口
五、 Ack
- 每个ack中必须包含Ack向量选项,准确地声明哪些包到达以及这些包是否被ecn标记
- 发送方使用Ack Radio来影响接收者产生DCCP-Ack封包的速率,从而控制反向路径的拥塞。Ack radio默认是2(一个ACK反应两个数据包),使用这个Ack Ratio的CCID 2的行为类似于TCP的延迟ack。
- Ack的拥塞控制:
1) 具体表现为:rtt时间内出现DCCP-ACK拥塞事件,发送方增大Ack Ratio,这样的话可以减小ack 速率;rtt时间内没出现DCCP-ACK拥塞事件,减小Ack Ratio
2) 如何检测丢失和标记的ack:
① 因为ack带有序列号,因此接收方的数据包在接收到序列号较大的NUMDUPACK数据包后就被认为丢失。
② Ack Ratio取决于接收方的非数据包的丢失和标记,因为非接收包对网络负担小。发送方能够通过标记分清非数据包和数据包。
发送方应该假设,为了计算Ack比率,每个丢失的包都是非数据包。
3) 改变ACK Ratio(附录A证明)
① ACK Ratio:整数,四舍五入不大于cwnd/2,对于四个或更多包的拥塞窗口,Ack比率大于2
② 改变方式:
有丢失或标记了DCCP-Ack包的数据的每个拥塞窗口(意味着有着拥塞了),Ack比率加倍;
对于每cwnd/(R^2 - R)个连续的拥塞窗口,没有丢失或标记的DCCP-Ack包,Ack比率减1。(并建议发送方在确定是否将Ack比率减1时使用cwnd的最新值)
③ 发送方不需要保持Ack比率完全最新。例如,它可以将Ack比率重新协商限制为每四五次rrt一次,或每一两秒一次
④ 综上所述,当cwnd = 1时,接收方总是为每个数据窗口发送至少一个确认,否则每个数据窗口至少发送两个确认 - Ack的ack
1) 当两个半连接都处于活动状态时,A对B的ack的应答自动包含在A对B数据的应答中。然而,如果b到a的半连接是不活动的,DCCP A必须偶尔主动发送确认。
2) 一旦发送方确认接收方的Ack向量并且发送方至少在T秒(T=max(0.2,2*rtt))内没有发送额外的数据,接收方可以推断发送方是静止的
六、 ECN
- 由于Ack向量中的信息是可靠传输的,因此DCCP不需要ECN-Echo和CWR的TCP标志。
- 由于CCID2的ACK也是拥塞控制的(之前说的ack的ack),ECN也可以用于它的确认。
TCP-like的RFC:http://www.rfc-editor.org/rfc/rfc4341.txt
七、总结
就我目前阅读的结果来看,TCP-like与TCP差别较大的地方在于对丢包的处理。我们知道TCP的ack是累积确认的,而TCP-like的ack返回的是当前收到序列号的最大值,并且TCP-like不重传。
那么TCP-like是怎么判断它丢包的呢?原文给出这样一段话:
也就是说在数据包P从发送端发送之后(但接收方没收到)接收方收到并确认NUMDUPACK(设定为3)个包,就可以判断包P丢失。丢失的话按照TCP中“重复确认”方式减半窗口和门限。
在实现上,这也算是对丢包的一种简化吧。
写了一个粗略的函数,其中,sent_yet_unacked是发送端对未收到ack包的序号统计字典。
def handle_duplicate_cnt(self,pkt):
# print(self.sent_yet_unacked)
for offset in list(self.sent_yet_unacked.keys()):#遍历字典中的未ack键值对,转成list才不会报错(不能在for循环中del字典)
for ack_offset in pkt.acked_offsets:#对收到的ack进行处理
if ack_offset > offset:#大于字典中的序号
self.sent_yet_unacked[offset] += 1
if self.sent_yet_unacked[offset] >=3:
self.ssthresh = max(1, self.cwnd * 0.5) # 门限窗口减半,拥塞窗口减半
self.cwnd = max(1, self.cwnd * 0.5)
self.update_cwnd(self.cwnd)
print('seq of lost packet',offset)
del self.sent_yet_unacked[offset]# 触发丢包后,删除无用计数
if ack_offset in self.sent_yet_unacked.keys():#收到该ack,可以删除
del self.sent_yet_unacked[ack_offset]