TCP系列32—窗口管理&流控—6、TCP zero windows和persist timer
一、简介
我们之前介绍过,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一般是针对有效数据来说的)。
二、wireshark示例
1、综合示例
我们设置tcp_retries2=6,tcp_invalid_ratelimit=1200,通过socket选项SO_RCVBUF设置server端和client端的window size如下图所示,通过这个示例我们还会进一步说明一下之前介绍过的延迟ACK的处理。
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
TCP系列32—窗口管理&流控—6、TCP zero windows和persist timer的更多相关文章
- TCP系列31—窗口管理&流控—5、TCP流控与滑窗
一.TCP流控 之前我们介绍过TCP是基于窗口的流量控制,在TCP的发送端会维持一个发送窗口,我们假设发送窗口的大小为N比特,网络环回时延为RTT,那么在网络状况良好没有发生拥塞的情况下,发送端每个R ...
- TCP系列27—窗口管理&流控—1、概述
在前面的内容中我们依次介绍了TCP的连接建立和终止过程和TCP的各种重传方式.接着我们在这部分首先关注交互式应用TCP连接相关内容如延迟ACK.Nagle算法.Cork算法等,接着我们引入流控机制(f ...
- TCP系列35—窗口管理&流控—9、紧急机制
一.概述 我们在最开始介绍TCP头结构的时候,里面有个URG的标志位,还有一个Urgent Pointer的16bits字段.当URG标志位有效的时候,Urgent Poinert用来指示紧急数据的相 ...
- TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS)
一.SWS介绍 前面我们已经通过示例看到如果接收端的应用层一直没有读取数据,那么window size就会慢慢变小最终可能变为0,此时我们假设一种场景,如果应用层读取少量数据(比如十几bytes),接 ...
- TCP系列34—窗口管理&流控—8、缓存自动调整
一.概述 我们之前介绍过一种具有大的带宽时延乘积(band-delay product.BDP)的网络,这种网络称为长肥网络(LongFatNetwork,即LFN).我们想象一种简单的场景,假设发送 ...
- TCP系列30—窗口管理&流控—4、Cork算法
一.Cork算法概述 Cork算法与Nagle算法类似,也有人把Cork算法称呼为super-Nagle.Nagle算法提出的背景是网络因为大量小包小包而导致利用率低下产生网络拥塞,网络发生拥塞的时候 ...
- TCP系列36—窗口管理&流控—10、linux下的异常报文系列接收
在这篇文章中我们看一下server端在接收到异常数据系列时的处理,主要目的是通过wireshark示例对这些异常数据系列的处理有一个直观的认识,感兴趣的自行阅读相关代码和协议,这里不再进行详细介绍 在 ...
- TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)
一.简介 之前的内容中我们多次提到延迟ACK(Delayed Ack),延迟ACK是在RFC1122协议中定义的,协议指出,一个TCP实现应该实现延迟ACK,但是ACK不能被过度延迟,协议给出延迟AC ...
- TCP系列29—窗口管理&流控—3、Nagle算法
一.Nagle算法概述 之前我们介绍过,有一些交互式应用会传递大量的小包(称呼为tinygrams),这些小包的负载可能只有几个bytes,但是TCP和IP的基本头就有40bytes,如果大量传递这种 ...
随机推荐
- Kafka 学习翻译 - 介绍
Kafka是一个分布式的流式平台.可以从几个方面理解: 1. 三个重要的能力: 能够实现流式的发布和订阅数据,类似于消息队列或者企业级的消息分发系统. 能够在提供一定容错性和持久性能力的基础上存储数据 ...
- TortoiseGit —— 配置密钥
TortoiseGit 使用扩展名为ppk的密钥,而不是ssh-keygen生成的rsa密钥.使用命令ssh-keygen -C "邮箱地址" -t rsa产生的密钥在Tortoi ...
- Python3练习:对员工信息文件,实现增删改查操作
1.练习要求: 2.数据文件(data_staff.txt) 1,Alex Li,22,13651054684,运维,2013-02-04 2,Jack Wang,20,13312331232,HR, ...
- STL---llist
#include<iostream> #include<algorithm> #include<list> using namespace std; struct ...
- 小程序开发-11-Promise正确用法与函数签名设计技巧
配置taBar "tabBar": { "selectedColor": "#000000", "backgroundColor& ...
- Multiclonal Invasion in Breast Tumors Identified by Topographic Single Cell Sequencing
Title: Multiclonal Invasion in Breast Tumors Identified by Topographic Single Cell Sequencing 课题的目的 ...
- c/c++链表的实现
#include<iostream> #include<string> #define SIZE 3 using std::cout; using std::endl; usi ...
- 苏醒的巨人----CSRF
一.CSRF 跨站请求伪造(Cross-Site Request Forgery,CSRF)是指利用 受害者尚未失效的身份认证信息(cookie.会话等),诱骗其点 击恶意链接或者访问包含攻击代码的页 ...
- Python全栈 进阶(进阶内容都在这了)
原文地址 https://yq.aliyun.com/articles/632754?spm=a2c4e.11155435.0.0.23eb3312feB6dG ................... ...
- 百度翻译api 实现简易微信翻译小程序
介绍 口袋翻译 口袋翻译 微信小程序 翻译功能 含7类语言的相互翻译 包含最近10条的翻译历史回溯功能 微信搜索:简e翻译 功能展示 使用百度翻译api需要申请 appid 与 key 并在 ap ...