TCP系列32—窗口管理&流控—六、TCP zero windows和persist timer

1、简介linux

        咱们以前介绍过,TCP报文中的window size表示发出这个报文的一端准备多少bytes的数据,当TCP的一端一直接收数据,可是应用层没有及时读取的话,数据一直在TCP模块中缓存,最终受限于接收缓存的大小,window size会变为0,此时咱们称呼这个接收窗口为零窗(zero window),对端也不能在发送更多的数据。若是随后本端应用层从TCP接收缓存中读取了足够数据,TCP模块有了足够的新的接收缓存的时候,就会发送一个TCP报文,并带有一个有效非零的Window size来指示对端本身已经能够接收新数据了。这个带有有效Window size的报文咱们称为窗口更新(window update)报文。窗口更新通常就是一个普通的ACK报文,并不会带有有效的数据(pure ACK),ACK报文不消耗系列号,若是发生丢失并不会进行重传。所以TCP须要处理window update消息丢失的场景。算法

        若是窗口更新报文发生丢失,那么接收端(这里的接收端是指window update消息的发送端)会等待发送端发送新的数据,而发送端会等待接收window update消息来发送新的数据,这种场景下,两端互相等待对方,就会产生一种deadlock(还记得Nagle算法和延迟ACK同时生效的时候也会产生相似的deadlock吧)。为了阻止这种死锁一直等待下去,TCP的发送端会使用一个persist timer定时器来定时查询接收端的window size是否增加,每当这个定时器超时的时候,发送端就会发送window probes报文。接收端在接收到window probe消息的时候会提供一个带有window size的ACK报文。RFC1122建议初始window probe定时器定时时间为RTO,随后进行window probe 的时候应该进行指数回退,最大指数回退次数为tcp_retries2,若是此时还没收到有效的window size,则会一直进行window probe过程(咱们以前经过示例介绍过RTO超时最后会释放链接,这个是与window probe的重要区别)。发送端。window probe报文中能够包含1byte的数据也可能不包含数据。当window probe包含数据的时候,接收端能够选择接收包含的数据也能够选择不接受包含的数据。关于persist timer,咱们前面在介绍cork算法的时候就接触过了。缓存

        另外在下面的示例中咱们将会看到,linux上发出的window probe消息是不带有有效数据的,并且window probe的系列号位于snd_nxt的前面,linux接收到这种报文的时候会认定这种报文为无效的系列号。对于这种类型的报文回复ACK时候受到参数tcp_invalid_ratelimit控制,这个参数控制了TCP对于这类系列号无效的报文的ACK回复速率,例以下面示例中咱们设置tcp_invalid_ratelimit=1200,含义就是说linux对于这类无效报文的ACK回复间隔最小为1200ms,只要间隔大于1200ms,linux就会当即回复一个dup ACK报文,并不受咱们以前介绍的延迟ACK策略的影响(延迟ACK通常是针对有效数据来讲的)。并发

2、wireshark示例socket

一、综合示例
tcp

咱们设置tcp_retries2=6,tcp_invalid_ratelimit=1200,经过socket选项SO_RCVBUF设置server端和client端的window size以下图所示,经过这个示例咱们还会进一步说明一下以前介绍过的延迟ACK的处理。ui

No1-No3:首先client和server端经过三次握手创建TCP链接,其中server端的window size为3000bytesspa

接着client端执行一次write操做,一次write写入5000bytes的数据。3d

No4:client端内核在从用户空间读取数据前会先获取当前的发送MSS,能够从图中看到,server端的接收窗口大小为3000bytes,可是MSS为65495bytes,显然不能按照这个MSS来发送,当出现这种状况的时候,linux的发送端(即本例中client端)会取对端最大接收窗口的一半1500bytes为发送mss。选定MSS后,接着linux内核会从client端尝试以1500bytes为单位来复制应用程序的数据(共5000bytes)而后发送出去,No4即对应client发出的第一个数据包。orm

No5:server端在接收到client端的No4数据包的时候,会初始化quick ACK模式,此时client端的rcv_mss为1500,rcv_wnd为3000,rcv_wnd/(2*rcv_mss)=1,所以quick ACK计数器初始化为1,对No4报文执行quick ACK反馈No5后,quick ACK计数器变为0,关于延迟ACK的相关内容能够参考前面系列文章

No6:接着client端内核继续从应用层复制1500bytes的数据并发送出去,此时client端一共发出了3000bytes的数据,而server端应用层一直没有读取TCP模块接收的数据。能够看到wireshark提示TCP Window Full信息。

No7:No5数据包发出去后quick ACK计数器变为0,此时server端对No6数据包执行延迟ACK策略,定时时间为40ms。从wireshark能够看到No7与No6数据包实际间隔大约为38ms,这种定时偏差问题是因为TCP模块的tick精度问题形成的,前面相关文章已经解释了,此处再也不赘言。server端的3000bytes已经所有被占用了,此时server端只能回复Window size为0的ACK报文,通知client本身再也不准备接收新数据了。能够看到wireshark提示了TCP zero Window提示信息。由于No7延迟了40ms回复ACK,因此当client收到这个报文的时候,client端的已经彻底把应用层的数据复制到了内核中,以前一次write写入了5000bytes数据,已经发出了3000bytes的数据,此时client端内核中还剩余2000bytes的数据待发送。client端内核虽然在收到No7报文以前就已经准备好发送数据了,可是因为window size限制而没发送出去。此时收到No7的ACK后会再次尝试发送剩余的2000bytes的数据,可是一样因为window size限制而发送失败(若是client忽视window size的限制强制发送,server端会怎么办?咱们后面文章在用示例来讲明),发送失败后,linux会判断若是当前已经已经发出的还未收到ACK确认包的报文个数为0而且还有待发送数据的话就会启动persist timer定时器,定时时间为RTO(当前RTO大约为208ms)。

No8:上面设置的persist timer定时器超时后,强制发送No8报文,注意No8报文的seq实际上比No7报文的ack number小1,并且Len=0,发送完这个报文后设置persist timer定时器,定时时间为上一次定时时间的2倍(大约为416ms)。wireshark对于No8报文的提示为TCP Keep-Alive,实际上这个报文的功能并非Keep-Alive的功能,后面文章咱们会介绍TCP Keep-Alive的。

No9:接着咱们看到server端对于No8报文当即回复了一个No9的ACK确认报文,这里起做用的并非quick ACK模式,而是linux对于相似No8这种window probe报文会认为是无效的系列号,只要当前时间距离上次回复无效系列号报文的ACK确认包时间超过了tcp_invalid_ratelimit参数设置的时间,那么linux就会当即回复ACK确认报文,能够看到这个ACK报文window size仍然为0。

No10:No8设置的定时器超时后,发出No10的window probe报文,并设置persist timer定时时间为4*RTO。

能够看到server端收到No10报文后并无当即回复ACK确认包,缘由是No10和No9的间隔时间并无超过1200ms的ACK发送间隔。

No11:persist timer再次超时,发出No11报文,并从新设置persist timer的定时时间为8*RTO

No12:server端在收到No11报文的时候,发现这个报文系列号无效,同时距离上一次回复无效系列号报文ACK确认包的时间(即No9的时间)已经超过了1200ms,所以当即回复ACK确认包。

No13-No18:这几个报文重复前面的指数回退过程,server端判断无效系列号报文的ACK间隔超过1200ms后当即回复ACK确认包。

No19:client在发送No19的window probe报文的时候发现,前面已经连续发送了No八、No十、No十一、No1三、No1五、No17共6个window probe报文,已经达到了tcp_retries2的配置值,所以随后client端不在进行指数回退的过程,对persist timer定时器的定时间隔固定为2^6*RTO,大约为13.312ms。能够看到这里没有释放TCP链接,而在RTO重传指数回退过程当中,当超过根据tcp_retries2计算的最大重传时间的时候就会释放TCP链接。

No20-No30:client端持续进行window probe过程,这个与上面处理相似,再也不多说

No31:接着在No30以后server端应用程序彻底读取出TCP中的3000bytes的缓存数据,server端发送window update消息给client端,通知对端能够发送新的数据

No32-No33:client端收到window update后,当即把剩余的2000byte分两个数据包发出

No34:server端收到No32报文的时候,发现距离上一次收到有效数据的时间超过了一个RTO,所以进入quick ACK模式,设置quick ACK计数器为rcv_wnd/(2*rcv_mss)=1,当即回复ACK确认包报文后,quick ACK计数器减1变为0

No35:server端在收到No33报文后,此时quick ACK计数器为0,进入延迟ACK处理,延迟ACK定时器超时后触发回复No35的ACK确认包。



补充说明:

一、MSS相对与发送窗口折半的限制处理,请参考tcp_bound_to_half_wnd

二、persist timer的定时器的初始启动__tcp_push_pending_frames,随后超时处理tcp_probe_timer

三、linux对于示例中window probe消息的处理以及与参数tcp_invalid_ratelimit的关系,参考tcp_validate_incoming和tcp_sequence





相关文章
相关标签/搜索