TCP系列32—窗⼝管理流控—6、TCPzerowindows和
persisttimer
⼀、简介
我们之前介绍过,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定时器来定时
520祝福语句查询接收端的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⼀般是针对有效数据来说的)。
⼆、wireshark⽰例
1、综合⽰例
我们设置tcp_retries2=6,tcp_invalid_ratelimit=1200,通过socket选项SO_RCVBUF设置server端和client端的window size如下图所⽰,通过这个⽰例我们还会进⼀步说明⼀下之前介绍过的延迟ACK的处理。
配置windows update失败No1-No3:⾸先client和server端通过三次握⼿建⽴TCP连接,其中server端的window size为3000bytes
接着client端执⾏⼀次write操作,⼀次write写⼊5000bytes的数据。
No4:client端内核在从⽤户空间读取数据前会先获取当前的发送MSS,可以从图中看到,server端的接收窗⼝⼤⼩为3000bytes,但是MSS 为65495bytes,显然不能按照这个MSS来发送,当出现这种情况的时候,linux的发送端(即本例中client端)会取对端最⼤接收窗⼝的⼀半1500bytes为发送mss。选定MSS后,接着linux内核会从client端尝试以1500bytes为单位来复制应⽤程序的数据(共5000bytes)然后发送出去,No4即对应client发出的第⼀个数据包。
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报⽂的时候发现,前⾯已经连续发送了No8、No10、No11、No13、No15、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确认包。
补充说明:
1、MSS相对与发送窗⼝折半的限制处理,请参考tcp_bound_to_half_wnd
2、persist timer的定时器的初始启动__tcp_push_pending_frames,随后超时处理tcp_probe_timer
3、linux对于⽰例中window probe消息的处理以及与参数tcp_invalid_ratelimit的关系,参考tcp_validate_incoming和tcp_sequence
>郑国霖女友