分类: LINUX
2013-01-22 14:24:28
原文地址:Inside TCP: 拥塞控制 作者:raise_sail
TCP_CA_Open
TCP_CA_Disorder
TCP_CA_CWR
TCP_CA_Recovery
TCP_CA_Loss
TCP连接的默认开始状态。这时TCP连接会在慢启动和拥塞避免阶段(调用tcp_cong_avoid)增加拥塞窗口。每个接收到的ACK都要调用 tcp_ack_is_dubious,检查它是否可疑,需要进一步处理。如果是ACK可疑,就调用 tcp_fastretrans_alert()就切换到其他CA状态。即使是可疑的ACK,如果tcp_may_raise_cwnd()允许,tcp_fastretrans_alert()仍然可能增大拥塞窗口。
这个状态代表TCP接收到dupack或者SACK,出现了乱序情况。此时,TCP会使用一些启发式方法判断是不是真的发生了包的丢失。
本状态说明发生某种拥塞,例如ICMP源抑制、本地设备拥塞,所以TCP发送方会放缓数据发送。
本状态代表发送方正在进行快速重传丢失的数据包。
如果发生RTO,或者接收到的ACK与发送方记录的SACK信息不同步,就会进入这个状态。
在慢启动阶段:
每到达一个ACK,snd_cwnd就加1。这意味着每个RTT,拥塞窗口就会翻倍。
慢启动门限:snd_ssthresh 记录了在慢启动阶段中 snd_cwnd 的最大值。
snd_cwnd 不会超过 snd_cwnd_clamp
在拥塞避免阶段:
一旦snd_cwnd 达到 snd_ssthreash,snd_cwnd 就会缓慢增长:每个RTT加1, snd_cwnd 不会超过 snd_cwnd_clamp。
如果以下任意条件为真,就认为这个ACK是可疑的:
ACK是重复的。
若以下所有条件为真:
输入数据包没有数据。
ACK没有更新接口窗口。
ACK没有确认新的数据。
如果以下任意条件为真,就代表网络处于拥塞状态:
输入包有SACK选项、置有ECE位、CA状态不是Open
在接收到可疑ACK后,如果满足以下所有条件,拥塞窗口也可以增大:
CA 状态不是 Recovery ,也不是CWR
没有设置ECE标志,或者拥塞窗口小于慢启动门限。
SACK reneging()
当接收到SACK时,它所确认的包会被标记为sacked。一般来说,正常的ACK会完全覆盖sacked的包;但如果ACK指向sacked包的中间,这很可能意味着SACK信息是损坏的,或者接收方发生了拥塞,已经丢掉了之前缓冲的数据包。这时,执行以下动作:
进入loss状态 ():
记录发生拥塞时的snd_nxt,保存于 high_seq。
设置Queue CWR 标志。(TCP_ECN_queue_cwr)
如果状态为CWR 或者Recovery (发送速率减半),使用prior_ssthresh 设置 ssthresh。如果是其他状态,prior_ssthresh = max(ssthresh, 0.75 * cwnd)
ssthresh = 拥塞窗口的一半,但必须大于2。
拥塞窗口设置为1
改变CA 状态为Loss
重传在发送队列前面的包 (之前被错误地标记为sacked)
是否进行恢复()
这个函数考察TCP连接上的各种参数(例如丢包的数量),以确定是不是进入恢复状态。如果一系列启发式检查表明目前网络中很可能是发生丢包了,检查内容包括:
有数据包丢失 (lost_out不为0)
如果sacked packet数量大于网络的reordering上限,就认为SACK是一个乱序数据包的确认。这时,可以假定发生丢包了。
如果发送队列的第一个包等待确认的时间已经有RTO了,就认为它丢失了。
如果以下三个条件为真,那么,TCP发送方便处于无新数据可发送并且确认的数据包数量已经足以认为其余数据已经在网络中丢失了:
发送出去还没有离开网络的(in flight)数据小于reordering上限 ( 条件2此时一定不满足)
发送出去还没有离开网络的(in flight)数据有一半以上已经sacked,或者sacked packets数量大于快速重传阀值。 (快速重传阀值,是发送方在决定进入快速重传前的接收到的dupack数量)
发送方不能发送任何数据,要么因为滑动窗口限制,要么因为应用程序没有交付新的数据。
如果决定进入恢复状态,CA状态被设置为Recovery。
尝试离开拥塞避免 ()
如果上述检查之后,没有进入恢复状态,tcp_try_to_open检查以下条件:
如果数据包置有ECE标志,进入CWR状态。调用tcp_cwnd_down()减少拥塞窗口,对端会得到通知(Queuing CWR)。
如果状态没有变为CWR,如果我们有任何sacked或者重传的数据包,就进入Disorder状态,并调用tcp_moderate_cwnd()。
Disorder状态下TCP的 ,仍然无法确定数据包丢失确凿与否。在有SACK之后的确认之后,可能会有常规的ACK一次确认大量数据,为了避免网络中出现burst流量,会将拥塞窗口设置为允许的最大burst个数据包,通常是3.
更新记分板 ()
这个函数会标记所有没有被SACK确认的数据包(直到SACK过的最大序号)为已丢失。此外,等待确认的时间等于RTO的数据包,也被标记为已丢失。丢失、sacked和发出统计都在这个函数内完成。
发送数据 ()
如果拥塞窗口允许,重传所有那些标记为已丢失但仍未重传过的数据包。
如果CA状态是Recovery (这意味着网络中确实发生了丢包) :
如果拥塞窗口允许,发送新的数据
如果不能发送新的数据,但拥塞窗口允许发送,就再次发送已经发送过数据(因为已经确证网络发生的丢包)。
处理其余的可疑ACK
其余可疑ACK的处理依赖于当前的CA状态:
1 如果当前状态是Recovery
如果确认了部分数据,就调用 :
tcp_may_undo : 检查是否已完成重传。如果有时间戳选项,就检查这个ACK是否是第一次发送的包的确认,这表明TCP是否是无必要地切换了CA状态,是不是应该撤消之前的操作。
如果tcp_may_undo 允许:
调用
撤消拥塞窗口的减少。如果prior_ssthresh为0,设置cwnd 为ssthresh。如果 prior ssthresh 非0,恢复 cwnd 为减少之前的值。恢复 ssthresh 为 prior_ssthresh。 (当不作撤消操作时,例如接收到ECE时,prior ssthresh 可能被设置为0)
调节被拥塞窗口,防止网络中出现burst (tcp_moderate_cwnd)
调用 TCP_ECN_withdraw_cwr(ref)
设置 is_dupack 为false ,防止重传后面的数据。
2 如果当前状态是 Loss
tcp_try_undo_loss: 如果确认了部分数据就调用,并且tcp_may_undo()允许的话,就调用tcp_undo_cwr 恢复拥塞窗口。
如果不允许撤消,就重新调整拥塞窗口 (tcp_moderate_cwnd) ,并调用tcp_xmit_retransmit_queue重传数据。
3 如果当前状态不是 Recovery 也不是 Loss
如果状态是 Disorder 调用 tcp_try_undo_dsack: [只要出现任何一个 D-sack,即意味着接收方接收了两次相同的数据,即重传是多余的,每个DSACK到达时都会减少undo_retrans 变量] 如果 undo_retrans 为0,调用 tcp_undo_cwr。
调用 tcp_time_to_recover 检查是否进入恢复状态,如果没有进入恢复状态,就调用 tcp_try_to_open并等待后续的确认。
如果进入恢复状态,调用 tcp_xmit_retransmit_queue 重传更多数据。
常规ACK的接收处理
当TCP发送方接收到Open状态中最后发送数据的最高序号的确认之后,CA状态机会重新进入Open状态。
1 如果当前状态是 Loss
试图恢复 ():
调用tcp_may_undo 检查确认是否是第一次发送的数据的,并检查是否可以撤消之前的操作。如果允许就调用 tcp_undo_cwr 恢复拥塞窗口和 ssthresh 。
在撤消拥塞窗口的操作之后,如果ACK的序号超过了high_seq,就切换到Open状态。否则,就等待后续的ACK进行状态切换。
2 如果当前状态是CWR
如果确认了大于 high_seq 的序号,这意味着CWR通知已经到达了对方。调用 tcp_complete_cwr减少 cwnd 到 ssthresh 。
切换到Open状态。
3 如果当前状态是Disorder
调用 tcp_try_undo_dsack 检查是否可能撤消对拥塞窗口的修改。
如果确认了大于 high_seq 的序号,就切换到 Open 状态。因为不再期待DSACK,这里切换状态是安全的。
4 如果当前状态是Recovery
调用 tcp_try_undo_recovery 恢复拥塞窗口,并切换到Open状态。
调用 tcp_complete_cwr 降低 cwnd 到 ssthresh 。
原文地址:
维护:
翻译:raise.sail@gmail.com