一、简介

之前的内容中我们多次提到延迟ACK(Delayed Ack),延迟ACK是在RFC1122协议中定义的,协议指出,一个TCP实现应该实现延迟ACK,但是ACK不能被过度延迟,协议给出延迟ACK的最大时间为0.5s。如果发送端连续发送最大的数据报文,那么没两个数据报文就需要回复一次ACK。延迟ACK主要目的是等待接收者应用层接收到数据处理后有可能会发送一个响应,这样ACK报文就可以和这个响应报文一起发送了,这样减少了网络中的数据包的同时,也降低了主机的负载处理压力。

二、linux实现概述

Linux在实现上有个延迟ACK定时器,这个定时器的定时时间保存在一个ato变量,这个ato的默认值为40ms,但是会动态调整,例如对于非交互式应用如果延迟ACK定时器超时的时候,ato变量的值就会倍增。但是延迟ACK定时器的最大定时时间为0.5s。与延迟ACK对应,linux还有一个quick ACK模式,这种quick ack模式下就会对每个数据包都回复一个ACK。在连接初始建立时候、收包间隔大于RTO时、收到不在接收窗内的报文的时候等场景下就会进入quck ack模式,进入quick ack模式的时候,会把quick ack计数器初始化为16(也有可能是小于16的某个值),这就是说随后的16个数据包都不采用延迟ACK。

具体到延迟ACK判断的时候,需要同时满足几个条件,如当前已经收到的但是还没有回复ACK确认包的报文小于接收MSS、当前没有处于quick ACK模式、当前缓存中没有先前接收的乱序报文、当前延迟ACK的超时时间大于ato/4等条件。

对于上面MSS的判断条件,linux采用保守的方法来估计对端的发送MSS(即本端的接收MSS,在linux内核中以rcv_mss变量维护),初始化的时候,首先会在SYN报文中的MSS值、SYN-ACK报文中的MSS值、接收窗口的一半、536bytes中取一个最小值,然后在这个最小值和88bytes中取一个最大值,即为接收MSS值,随后在这个TCP连接上面收到新的数据包的时候,一旦发现这个数据包比之前的接收MSS值大,就会把接收MSS值更新为这个数据包的大小。根据MSS这个判断条件,在TCP高速传输的时候,一般都是以MSS发送数据,因此延迟ACK时候会每两个报文回复一个ACK确认包。

quick ACK模式可以通过socket编程接口TCP_QUICKACK选项进行设置,但是这个设置只是设置了一个quick ACK模式的切换,随后TCP的内部处理还可能会切换quick ACK模式。也可以通过路由表永久设置打开quick ACK 模式,例如下面就设置了127.0.0.1这条路由开启quick ACK。

  1. ******@Inspiron:~$ sudo ip route change local 127.0.0.1 dev lo quickack 1
  2. ******@Inspiron:~$ ip route show table all| grep 'local 127.0.0.1'
  3. local 127.0.0.1 dev lo  table local  scope host  quickack 1

上面只是根据linux实现代码概述了一下延迟ACK的实现,还有一些偏僻细节并没有提到,感兴趣的自行参考linux的实现代码。下面我们根据上面的描述来看一个延迟ACK的wireshark示例。

三、wireshark示例

我们通过一个综合实例来看一下延迟ACK模式的处理,示例图中高亮的数据包是我标记出来的以方便查看,并非是wireshark自动标记的异常包。在后面介绍其他内容后我们还会穿插延迟ACK的示例。

1、综合示例

首先在连接初始建立的时候,按照上面描述会进入quick ACK模式,quick ACK计数器初始化为16,连接初始建立进入quick ACK的目的主要是加速初始慢启动过程。从下图可以看到,从第三个数据包开始,client没发送一个TCP报文,server端就会立即回复ACK确认包。另外从图中可以看到此时client的SYN包中MSS为62,server端SYN-ACK报文中的MSS为65495,server端的接收窗口大小为43690,此时初始化接收MSS过程为,先计算min(62-12, 65495-12, 43690/2, 576)=62bytes,因为linux内部维护的MSS扣除了TSopt选项的12bytes,所以min计算中需要对两端通告的MSS先减12。接着计算max(62, 88)=88bytes,即此时server端认为接收MSS为88byte。一旦后面进入延迟ACK模式后,如果接收的还没有回复ACK确认的报文总大小超过88bytes的时候就会立即回复ACK报文。

接着我们看到从No36报文开始,server并不会对每个收到的TCP报文立即回复ACK了,即进入了延迟ACK模式。而之前的No4-No35共32个报文,包含16个client发过来的数据报文,16个server端回复的ACK报文,正好与quick ACK的初始值16对应。接着从No36开始,server端收到No36的时候判断已经推出了快速ACK模式同时其他条件也满足需要延迟ACK,启动延迟ACK定时器,定时时间为40ms,但是server端在接收到No38报文的时候发现累计没有回复ACK确认包的总数据量为120bytes超过了接收88bytes的接收MSS,因此立即回复了ACK确认包并取消延迟ACK定时器(根据编译内核时候的配置也可能并不取消延迟ACK定时器,但是延迟ACK定时器超时后会判断当前ACK已经发送而不再重复发送ACK)。随后的No40-No43和No44-No47报文的处理流程与No36-No39类似不在重复描述

接着从No48报文开始,报文长度变为20bytes,这样要超过88bytes的接收MSS就需要5个报文,可以从图中看出,server端正是在接收到No48-No52共五个报文100bytes后才触发了No53的ACK回复。可以看出No53和No48之间大约是12ms,并不是延迟ACK超时触发的No53确认包回复。接着注意在No58处server端收到长度为150byte的数据包,server更新接收MSS为max(当前接收MSS, 150)=150bytes。接着从No60报文开始可以看到需要连续4个40bytes的报文才会立即触发ACK回复(注意与上面接收MSS为88bytes,报文长度同为40bytes时候需要3个报文触发ACK的场景进行对比)。

我们接着看No70-No73四个数据包总长度为140bytes低于150bytes的接收MSS,因此并没能触发ACK回复,最终延迟ACK定时器超时,回复No74确认包。此时ato更新为80ms,但是收到No75数据包的时候,ato又更新为60ms(ato更新这块有点罗嗦,已经超出了本文的范围,所以知道这个东西会更新就行了,想深入的可以去看代码),因此延迟ACK定时器设置为60ms。No75-No78四个数据包总共120bytes没有超过接收MSS,因此最终延迟ACK定时器超时,回复No79确认包,但是我们看到No79和No75之间时间差大约为58ms,与预期的60ms有些差距,原因是ubuntu16.04编译内核默认的宏CONFIG_HZ=250,即1s为250tick,这个tick是TCP模块使用的最小时间单位,即4ms,因此定时的时候有可能最大有4ms的误差。可以从自行添加的内核log中确认此时的ACK延迟定时器为60ms。这个定时器误差在TCP模块中的其他各种定时器中也是可能会出现的。接下来No80-No84五个数据包总共为145bytes,没有满足150bytes的接收MSS,但是从图中可以看到No84仍然立即触发了ACK回复,原因是server端接收到No84数据包后,发现当前ato为40ms,距离触发延迟ACK定时器的时间已经不足ato/4了,因此立即触发了No85的ACK确认包。接着No86和No87直接同样是延迟ACK超时。

接着因为No88与上一个数据包No86之间的时间差超过了RTO的值,server端重新进入quick ACK模式,并把quick ACK计数器初始化为16,对接下来的16个数据包立即回复ACK确认包,如下图所示

2、通过路由表设置quick ACK模式

当执行下面语句设置quick ACK后,重新执行上面的程序,不会再出现延迟ACK的情况,限于篇幅不再附wireshark截图,感兴趣的可以自行去git下载

  1. sudo ip route change local 127.0.0.1 dev lo quickack 1

3、TLP与延迟ACK交互

在低时延场景下,TLP与延迟ACK交互可能会造成无效重传,示例请参考后面SWS介绍文中的wireshark示例。

补充说明:

1、在MAC OS上可以通过设置net.inet.tcp.delayed_ack来配置延迟ACK,设置为0表示禁止延迟ACK,设置为1表示总是延迟ACK,设置为2表示每两个数据包回复一个ACK,设置为3标识系统自动探测回复ACK的时机

2、windows可以通过在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\IG条目下设置TcpAckFrequency参数来配置延迟ACK,可以通过TcpdelAckTicks来设置延迟ACK定时器的定时时间

TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)的更多相关文章

  1. TCP系列27—窗口管理&流控—1、概述

    在前面的内容中我们依次介绍了TCP的连接建立和终止过程和TCP的各种重传方式.接着我们在这部分首先关注交互式应用TCP连接相关内容如延迟ACK.Nagle算法.Cork算法等,接着我们引入流控机制(f ...

  2. TCP系列31—窗口管理&流控—5、TCP流控与滑窗

    一.TCP流控 之前我们介绍过TCP是基于窗口的流量控制,在TCP的发送端会维持一个发送窗口,我们假设发送窗口的大小为N比特,网络环回时延为RTT,那么在网络状况良好没有发生拥塞的情况下,发送端每个R ...

  3. TCP系列35—窗口管理&流控—9、紧急机制

    一.概述 我们在最开始介绍TCP头结构的时候,里面有个URG的标志位,还有一个Urgent Pointer的16bits字段.当URG标志位有效的时候,Urgent Poinert用来指示紧急数据的相 ...

  4. TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS)

    一.SWS介绍 前面我们已经通过示例看到如果接收端的应用层一直没有读取数据,那么window size就会慢慢变小最终可能变为0,此时我们假设一种场景,如果应用层读取少量数据(比如十几bytes),接 ...

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

    一.简介 我们之前介绍过,TCP报文中的window size表示发出这个报文的一端准备多少bytes的数据,当TCP的一端一直接收数据,但是应用层没有及时读取的话,数据一直在TCP模块中缓存,最终受 ...

  6. TCP系列29—窗口管理&流控—3、Nagle算法

    一.Nagle算法概述 之前我们介绍过,有一些交互式应用会传递大量的小包(称呼为tinygrams),这些小包的负载可能只有几个bytes,但是TCP和IP的基本头就有40bytes,如果大量传递这种 ...

  7. TCP系列36—窗口管理&流控—10、linux下的异常报文系列接收

    在这篇文章中我们看一下server端在接收到异常数据系列时的处理,主要目的是通过wireshark示例对这些异常数据系列的处理有一个直观的认识,感兴趣的自行阅读相关代码和协议,这里不再进行详细介绍 在 ...

  8. TCP系列34—窗口管理&流控—8、缓存自动调整

    一.概述 我们之前介绍过一种具有大的带宽时延乘积(band-delay product.BDP)的网络,这种网络称为长肥网络(LongFatNetwork,即LFN).我们想象一种简单的场景,假设发送 ...

  9. TCP系列30—窗口管理&流控—4、Cork算法

    一.Cork算法概述 Cork算法与Nagle算法类似,也有人把Cork算法称呼为super-Nagle.Nagle算法提出的背景是网络因为大量小包小包而导致利用率低下产生网络拥塞,网络发生拥塞的时候 ...

随机推荐

  1. Apache常规配置说明

    Apache配置文件:conf/httpd.conf.(注意:表示路径时使用‘/’而不使用‘\’,注释使用‘#’) 1. ServerRoot:服务器根目录,也就是Apache的安装目录,其他的目录配 ...

  2. hiveserver2不能启动

    我的hiveserver2一直不能启动,命令行一直卡住不动,然后我就想是不是配置文件没有配置相关的参数,然后就来修改hive-site.xml 最终修改完后的hive-site.xml: <?x ...

  3. Python学习:11.Python装饰器讲解(二)

    回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...

  4. ...续上文(一个小萌新的C语言之旅)

    我们继续上次没介绍完的继续讲: 下面我们说一下二进制,二进制是计算技术中广泛采用的一种 数制. 二进制数据是用0和1两个 数码来表示的数.它的基数为2,进位规则是“逢二进一”.那么二进制怎么转化为十进 ...

  5. JavaScript基础part1

    JavaScript介绍 你不知道它是什么就学?这就是一个网页嵌入式脚本语言...仅此而已 JavaScript组成 一个完整的 JavaScript 实现是由以下 3 个不同部分组成的: 核心(EC ...

  6. 北京Uber优步司机奖励政策(3月3日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  7. GitHub 配置指南

    Git和GitHub的区别 GitHub术语解析 配置使用 注册GitHub帐号 创建Git 创建库 复制库 社交化 Git和GitHub的区别 Git是一个分布式的版本控制系统,与SVN类似:最初由 ...

  8. iReport jasperReports 生成表格

    使用iReport生成表格   一 环境:iReport-5.6.0  JDK7 1.注意,iReport的最新版本目前还不支持JDK8,如果项目工程已经配置了JDK8,那也不用去修改环境变量和工程的 ...

  9. vcf-tools 笔记

    vcf-query: 通过 vcf-query 提取DP (reads depth). ~/zengs/Tools/vcftools/perl/vcf-query -f '%CHROM\t%POS\t ...

  10. WPF & EF & Prism useful links

    Prism Attributes for MEF https://msdn.microsoft.com/en-us/library/ee155691%28v=vs.110%29.aspx Generi ...