解决TCP延迟应答(Delay ACK)问题的3个小Trick-(续:正规的做法)

Posted dog250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决TCP延迟应答(Delay ACK)问题的3个小Trick-(续:正规的做法)相关的知识,希望对你有一定的参考价值。

这个话题确实有点乱,事实就是如此。
        在《 解决TCP延迟应答(Delay ACK)问题的3个小Trick》中,我描述了一种通过修改发送端协议栈的方式来消解Delay ACK带来的危害的方案,这实属一种无奈的走火入魔的做法,因为你无法控制数据接收端,因为总是有人认为网络上的任何所谓的“延迟”都是坏事,完美主义者总是试图消解任何的延迟!但事实上...
        再次重申,不要以为Delay ACK是什么坏事,千万不要!正如不要认为拥塞窗口越大越好一样,任何人都无法用一句绝对的话来描述TCP!大多数情况下Delay ACK是会带来收益,如果它确实给你带来了损失,那一定是你误用了它,而不是协议栈实现的错!那么怎么正确使用Delay ACK,这是本文的主题!站在这个立场上,我之前的一篇文章《 解决TCP延迟应答(Delay ACK)问题的3个小Trick》纯属娱乐,因为Delay ACK被设计出来是让人正确使用的,而不是让人来消解的!

        本文描述的Trick是正规的做法,它涉及到几个TCP的socket选项。我试图用简短的篇幅来描述,因为我还有关于OpenVPN更重要的事情要做,我要珍惜这个周末。
        关于本文的主题,行文之前我落实一个事实:不要试图通过一个参数禁用Delay ACK,因为协议栈会动态调整到底要不要Delay,也不要觉得Delay ACK是个错误,十有八九,反对Delay ACK的人是个错误!

1.TCP_NODELAY选项

这个选项与Delay ACK无关,这个选项是与发送端相关的,它只是禁用了Nagle算法。

2.TCP_QUICKACK选项

这个选项貌似禁用了接收端的Delay ACK,但事实上并不总是。manual看一下你就会知道全部,man tcp的结果:
TCP_QUICKACK (since Linux 2.4.4)
    Enable  quickack  mode  if  set or disable quickack mode if cleared.  In quickack mode, acks are sent immediately, rather than delayed if
    needed in accordance to normal TCP operation.  This flag is not permanent, it only enables a switch to or from quickack mode.  Subsequent
    operation  of  the  TCP  protocol will once again enter/leave quickack mode depending on internal protocol processing and factors such as
    delayed ack timeouts occurring and data transfer.  This option should not be used in code intended to be portable.

3.TCP_CORK选项

这个我在2014年的春天写过一篇文章《 再次谈谈TCP的Nagle算法与TCP_CORK选项》,本文不再赘述。这也是一个发送端的行为控制。

4.Linux协议栈中的TCP pingpong

看了上面的1,2,3,好像什么都没说的样子,所以我要在4里说!
引用前一篇文章的一段话:
延迟应答在单向数据传输中,没有半点危害,延迟应答本身就是为提高单向数据传输的吞吐量而设计的,然而对于交互场景,延迟应答会影响响应度,正如效率和公平之间的关系一样,吞吐量和响应度之间也是互斥的关系,明白了点什么吗?TCP是一个传输层协议,并不理解应用层的语义,因此TCP并不知道应用层是”单向传输“还是”交互“,正如HTTP的例子一样,在下载一个大文件(典型的单向传输场景)之前,浏览器会和WEB服务器交互几组数据(典型的交互场景),请问TCP知道这些吗?如果知道,它当然就会知道什么时候要延迟应答而什么时候不要,不幸的是,TCP并不知道,这又是一个TCP又瞎又傻的案例!跟蝙蝠或者拉二胡的阿炳一样,TCP是个大瞎子,和蝙蝠或者拉二胡的阿炳不同的是,TCP还是个二傻子。
然后你就知道了关于TCP_QUICKACK的manual里说的那段的具体原因了:
This flag is not permanent, it only enables a switch to or from quickack mode.  Subsequent
operation  of  the  TCP  protocol will once again enter/leave quickack mode depending on internal protocol processing and factors such as
delayed ack timeouts occurring and data transfer.

        是的,关于是否启用Delay ACK,并不是一个参数控制的,而是TCP协议本身控制的!
        TCP在运行过程中,会动态地启用,禁用Delay ACK,然而它提供了一个socket选项接口,让你可以随时去禁用或者启用Delay ACK。如果作为强迫症患者的你想永久禁用Delay ACK,那就单独开启一个线程,在里面写一个无限循环:
while TRUE
    setsockopt TCP_QUICKACK;
end while
但是几乎没有人这么做。
        那么TCP到底在什么时候会启用Delay ACK呢?答案是当它发现这个连接是一个“交互连接”的时候,即该连接是一个在两个方向都有频繁的数据传输!额外的约束是,即便TCP发现了一个连接是一个交互连接,也只能最多缓存两个MSS的连续数据,还有另外另个约束,那就是:
1).收到乱序数据的时候,即便已经处在Delay ACK模式,也要立即回复ACK;
2).乱序的数据空洞被填补后,即便已经处在Delay ACK模式,也要立即回复一个ACK。

以上的逻辑是 RFC2581的4.2节规定的。
在上述的规则和约束下,Linux的TCP协议栈维护了一个pingpong变量以及quick计数器。pingpong变量指示现在是否处在Delay ACK模式,而quick计数器指示可以发送延迟ACK的份额。 
...
        以上依然什么都没说!现在该说了!
        TCP只要发现当前的通信模型是交互的,即数据是有收有发的,那就会将pingpong变量设置成1,此时TCP更加期待的是自己这边发送数据时“捎带ACK”,而不是自发的ACK!因此,如果当前的模式是交互模式,那么Delay ACK就会被启用,反过来,如果TCP认为当前的模式是单向传输模式,那么它更希望的是即时ACK。然而复杂性在于,即便是TCP预测当前的模式是单向传输模式,也不是无条件的即时ACK,而是试图缓存最多2个MSS的数据进行“积累ACK”,这好像是与前面的观点相左,但是请注意,这里考虑的是发送端的行为,即Nagle之类算法的影响!在单向传输模式下,如果发送端启用了Nagle,那么接收端需要配合的就是,尽量只收满载MSS的数据!
        这会对发送端造成危害吗?请暂时忘掉Nagle,忘掉CORK...如果你记得慢启动,那你就是好样的!慢启动的最小份额是2个MSS!其在Delay ACK的容忍度以外,没有任何问题!
        最后,我告诉程序员们一个事实,那就是,大多数运营商或者中间设备都会照顾不携带任何数据的纯ACK(这一点对于一些明智的人是可以利用的,但是对于处在规则之内的员工,算了...)!因此在计算丢包率的时候,关于ACK的丢失一定要加权,权值小于1!
【关于我没有涉及的内容】我没有设计关于Linux TCP实现中的ATO问题,我没有涉及ATO与RTO的关系,我没有设计ATO是多少时pingpong应该设置成1或者0,而这些对于一个程序员而言,如果你懂了原理,就应该知道该怎么办,如果你不知道,请辞职!

       最后,正规的做法是什么呢?没有!
       正确的做法是,把路由与交换搞起,请记住,不要忽略网络,我要告诉你,TCP亵渎了网络,就像炒房的人亵渎了经济一样!

以上是关于解决TCP延迟应答(Delay ACK)问题的3个小Trick-(续:正规的做法)的主要内容,如果未能解决你的问题,请参考以下文章

TCP协议

传输层协议 ——— TCP协议

TCP / IP 协议

计算机网络传输层TCP协议

计算机网络协议复习 —— TCP/IP。。。

计算机网络协议复习 —— TCP/IP。。。