TCP系列29—窗口管理&流控—3、Nagle算法
一、Nagle算法概述
之前我们介绍过,有一些交互式应用会传递大量的小包(称呼为tinygrams),这些小包的负载可能只有几个bytes,但是TCP和IP的基本头就有40bytes,如果大量传递这种小包,会严重降低网络利用率,还可能造成网络拥塞。福特公司就曾经遇到过这种问题,John Nagle提出了一种通过ACK报文控制TCP发包的方法解决了这种问题,这种方法也就以Nagle名字命名,称为Nagle算法。Nagle算法最开始的标准为RFC896,但是RFC896目前已经被RFC7805移动到了Historic状态,原因是RFC1122和RFC6633描述的内容已经取代了RFC896。
二、Nagle算法概述
用一句话简单的描述Nagle算法就是:在任意时刻,最多只能有一个未被ACK确认的小包。
按照RFC1122描述,如果有发送出去的但是还没有被ACK确认的数据,那么发送方应该缓存所有的用户数据,直到发出去的数据被ACK确认或者缓存的TCP数据可以发出一个full-sized的报文。full-sized报文一般是指达到了SMSS的报文。这样实际上按照这样的要求,对于小包的处理,TCP实际上进入了停等式(stop-and-wait)。这里的协议描述并没有限制发送出去还未确认的报文是否为小包,而在linux实现上则会进一步判断如果有发出去的还没确认的小包(小于SMSS的报文)才会进一步判断是否使能Nagle算法(请参考wireshark示例)。
RFC1122还说明一个TCP主机应该实现Nalge算法来把小包汇聚,但是一定要实现一个接口可以让应用层单独在一个TCP连接上禁用Nagle算法。linux中Nagle算法默认是打开的,应用程序可以通过TCP_NODELAY选项来设置socket关闭Nagle算法(注意是关闭)。
三、Nagle算法与延迟ACK
当Nagle算法和延迟ACK同时使能的时候,可能会造成发送端等待ACK确认包以发送剩下的小包数据,而接收方等待应用层数据或者等待接收新的数据以超过一个接收MSS后在发送ACK确认包。这样发送发和接收方互相等待对方(称呼为deadlock),最终延迟ACK超时。这种情况下就会提升平均时延,降低网络性能,因此可能需要禁用Nagle算法或者延迟ACK算法。参考下面的wireshark示例。
四、wireshark示例
在下面的相关测试中需要关闭网卡的TSO和GSO功能,关闭方法
ethtool -K lo tso offethtool -K lo gso off
查看网卡TSO和GSO相关配置可以通过下面命令查看
ethtool -k lo
TSO和GSO功能是指TCP模块发送报文的时候,可以以整数倍的MSS大小发送,然后网卡进行分段,最终发出去的报文是没有超过MSS的,但是wireshark在TSO之前抓包,看到的数据包的大小会为MSS的整数倍。例如下面示例1如果不关闭TSO和GSO功能,则第一次write写入105bytes的数据后,wireshark会显示先发送了100bytes的数据,然后又发送了5bytes的数据。
另外下面示例图中,除了TLP和Nagle交互的示例外,其余示例的截图中的高亮的数据包是我为了解释标记出来的,并不是wireshark标记的异常包。
1、默认打开Nalge算法场景
如下图所示,server与client建立连接后,client发送一个No4数据包,server回复ACK确认,连接正常建立。其中在No1报文中可以看到client端通告的MSS选项中MSS为62bytes。server端扣除TSopt选项的12bytes后(其中还包括两个nop选项),最大只能发送50bytes的数据。接着server端连续两次write写入,第一次写入105bytes,第二次写入58bytes。可以看到在以MSS大小发出去No6和No7报文之后,剩余的5bytes同样也发出去了,并没有因为之前有未ACK确认的full-sized数据包而阻止发送。接着第二次写入58byte后,其中前50bytes可以构成一个full-sized包,因此可以直接发送出去,但是剩余的8bytes因为不能构成一个full-sized报文,同时还没有收到前面No8这个小包的ACK确认包,因此这8bytes会暂时被Nagle算法阻止发送,直到收到No8的确认包No12后,最后的8bytes才得以传输出去。

2、设置关闭Nagle场景
同样运行上面的测试用例,但是这次server端代码中通过TCP_NODELAY设置关闭Nagle算法。结果如下图,与上面示例对比,这里不再额外解释。

3、Nagle算法和延迟ACK同时打开的场景
client与server端建立连接后先写入20个数据包,每个包的长度为58bytes,接着client每隔3ms写入3bytes的数据,最终结果如下图所示
之前我们已经介绍过一般连接建立后第36个数据包以后才会启动延迟ACK,从下图可以看到No36的确认包No37确认发生了延迟。但是No38的确认包并没有延迟,原因是No39需要更新窗口(对比图中No39和No37的Win字段)。No40以后可以看到基本上每次server端回复ACK都需要延迟等待大约40ms,而client受限与nagle算法只能等到server端回复ACK确认包后才能发出新的数据。可以看到这个时候,client端每个数据包的平均延迟被将会变大,而且延迟ACK的延迟时间ato还会动态调整(一般最大不超过RTO),所以对于时延比较敏感的交互式应用应该关闭Nalge算法。
在打开Nagle算法的场景下,测试完成后wireshark显示总共交换了87个数据包,总耗时1.004s,关闭Nagle算法后进行同样的测试,wireshark显示总共交换了361个数据包,总耗时0.967s。另外从应用层看每个数据包的平均耗时打开Nagle算法和关闭Nagle算法对比,将会更长。关闭Nagle算法测试大的wireshark抓包文件可以自行下载查看,此处不再赘述。

4、Nagle和TLP交互
之前我们介绍TLP的时候,曾经提到过TLP的Loss Probe探测包并不受到Nagle的约束,示例如下,No10为TLP的Loss Probe报文,可以看到并没有受到Nagle算法的约束。

补充说明:
1、windows下面可以通过添加DWORD注册表项HKLM/SOFTWARE\Misrosoft\MSMQ\Parameters\TCPNoDelay全局关闭Nagle算法。
TCP系列29—窗口管理&流控—3、Nagle算法的更多相关文章
- TCP系列27—窗口管理&流控—1、概述
在前面的内容中我们依次介绍了TCP的连接建立和终止过程和TCP的各种重传方式.接着我们在这部分首先关注交互式应用TCP连接相关内容如延迟ACK.Nagle算法.Cork算法等,接着我们引入流控机制(f ...
- TCP系列31—窗口管理&流控—5、TCP流控与滑窗
一.TCP流控 之前我们介绍过TCP是基于窗口的流量控制,在TCP的发送端会维持一个发送窗口,我们假设发送窗口的大小为N比特,网络环回时延为RTT,那么在网络状况良好没有发生拥塞的情况下,发送端每个R ...
- TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS)
一.SWS介绍 前面我们已经通过示例看到如果接收端的应用层一直没有读取数据,那么window size就会慢慢变小最终可能变为0,此时我们假设一种场景,如果应用层读取少量数据(比如十几bytes),接 ...
- TCP系列32—窗口管理&流控—6、TCP zero windows和persist timer
一.简介 我们之前介绍过,TCP报文中的window size表示发出这个报文的一端准备多少bytes的数据,当TCP的一端一直接收数据,但是应用层没有及时读取的话,数据一直在TCP模块中缓存,最终受 ...
- TCP系列30—窗口管理&流控—4、Cork算法
一.Cork算法概述 Cork算法与Nagle算法类似,也有人把Cork算法称呼为super-Nagle.Nagle算法提出的背景是网络因为大量小包小包而导致利用率低下产生网络拥塞,网络发生拥塞的时候 ...
- TCP系列36—窗口管理&流控—10、linux下的异常报文系列接收
在这篇文章中我们看一下server端在接收到异常数据系列时的处理,主要目的是通过wireshark示例对这些异常数据系列的处理有一个直观的认识,感兴趣的自行阅读相关代码和协议,这里不再进行详细介绍 在 ...
- TCP系列35—窗口管理&流控—9、紧急机制
一.概述 我们在最开始介绍TCP头结构的时候,里面有个URG的标志位,还有一个Urgent Pointer的16bits字段.当URG标志位有效的时候,Urgent Poinert用来指示紧急数据的相 ...
- TCP系列34—窗口管理&流控—8、缓存自动调整
一.概述 我们之前介绍过一种具有大的带宽时延乘积(band-delay product.BDP)的网络,这种网络称为长肥网络(LongFatNetwork,即LFN).我们想象一种简单的场景,假设发送 ...
- TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)
一.简介 之前的内容中我们多次提到延迟ACK(Delayed Ack),延迟ACK是在RFC1122协议中定义的,协议指出,一个TCP实现应该实现延迟ACK,但是ACK不能被过度延迟,协议给出延迟AC ...
随机推荐
- 小白CSS学习日记-----杂乱无序记录(3)
1.后代选择器 .antzone li { } class='antzone' 所有子孙后代中的li 2.子选择器 .antzone > li { } class='antzone' 的子一 ...
- 动态的GRE OVER IPSEC的实验模拟与分析
此篇博客正在介绍的是下图中的Dynamic P2P GRE OVER IPSEC VPN: 为什么出现这种动态的GRE OVER IPSEC VPN技术呢? 首先在前面几篇博客中已经介绍过了,动态是为 ...
- C语言实例解析精粹学习笔记——19
实例19:判断正整数n的d进制表示形式是否是回文数(顺着看和倒着看相同的数). 主要思路: 一种方法:将正整数n数转换成d进制的数,逐个比较首尾对应数字,判断是否为回文数. 另一种方法:将正整数n数转 ...
- PTA基础编程题目集7-2然后是几点
有时候人们用四位数字表示一个时间,比如1106表示11点零6分.现在,你的程序要根据起始时间和流逝的时间计算出终止时间. 读入两个数字,第一个数字以这样的四位数字表示当前时间,第二个数字表示分钟数,计 ...
- golang 并发执行函数func类型slice
golang的slice支持func.使用func slice要注意func要完整描述入参出参. 如果需要执行一系列类型相同(入参出参格式相同)的函数,可以动态添加到一个slice里面.range s ...
- java的编码格式
几种常见的编码格式 为什么要编码 不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言 ...
- eclipse注释任务标记
一.概述 TODO: + 说明: 如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明. FIXME: + 说明: 如果代码中有该标识,说明标识处代码需要修正,甚至代码是 ...
- Java面向对象之抽象方法&接口
在开始写抽象类之前,有一个问题我觉得想清楚会对理解抽象类很有帮助:那就是为什么要设计抽象类? 难道用类还不够么,为什么要设计出抽象类这样一个东西.我们可以换个角度来理解,就是有些类本来就是不应该被实例 ...
- C#监听锁屏代码
今天,偶然间在技术群看有人问,怎么监听锁屏. 在此处记录一下 public class Constrctor { public Constrctor() { SystemEvents.SessionS ...
- WEB安全--高级sql注入,爆错注入,布尔盲注,时间盲注
1.爆错注入 什么情况想能使用报错注入------------页面返回连接错误信息 常用函数 updatexml()if...floorextractvalue updatexml(,concat() ...