您的位置:首页 > 理论基础 > 计算机网络

网络编程中的Socket详解---Delayed Ack(Ack确认延迟) && Nagle Algorithm(纳格算法)

2017-11-17 14:28 561 查看
今天开始学习Socket编程,但是上网查询的一些资料之后发现与之相关的知识太多了,所以我从基础看起,慢慢来,首先来看一下Delayed Ack 和 Nagle Algorithm的内容。

1.Delayed Ack 

tcp协议规定在接受到数据段时需要向对方发送一个确认,但如果只是单纯的发送一个确认,代价会比较高(20字节的ip首部,20字节的tcp首部),最好能附带响应数据一起发送给对 方.所以tcp在何时发送ack给对方有以下规定: 

1) 当有响应数据要发送时, ack会随响数据立即发送给对方 . 

2) 如果没有响应数据,ack的发 送将会有一个延迟,以等待看是否有响应数据可以一起发送 ,这称是"Delayed Ack".但这个延迟最多不会超过500ms,一般为200ms.如果在200ms内有数据要发送,那么ack会随数据一起立即发送给对方.注意这里的延迟200ms,不是指的从接受到对方数据到发送ack的最长等待时间差.而是指的内核启动的一个定时器,它每隔200ms就查看下是否有ack要发送.例如:假设定时器在0ms时启动,对方的数据段在 

185ms时到达,那么ack最迟会在200ms时发送,而不是385ms时发送. 

3) 如果在等待发送ack期间,对方的第二个数据段又到达了,这时要立即发送ack.但是如果对方的三个数据段相继 到达,那么第二个数据段到达时ack立即发送,但第三个数据段到达时是否立即发送,则取决于上面两条.

2.Nagle Algorithm 

当tcp协议用来传输小的数据段时代码是很高的,并且如果传输是在广域网上,那可能就会引起网络拥塞.Nagle算法就是用来解决这个问题.该算法要求一个TCP连接上最多只能有一个 未被确认(未收到Ack确认) 的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反TCP收集这些少量的分组,并在确认到来时以一 个分组的方式发出去.Host Requirements RFC声明TCP必须实现Nagle算法,但必须为应用提供一种方法来关闭该算法在某个连接上执行。 

纳格算法是合并(coalescing)一定数量的输出资料后一次送出。特别的是,只要有已送出的 封包尚未确认,传送者会持续缓冲封包,直到累积一定数量的 资料 才送出。 

算法如下如下:

if 有新资料要传送 

if 讯窗大小 >= MSS and 可传送的资料 >= MSS 

  立刻传送完整MSS大小的segment 

else 

  if  管线中有尚未确认的资料 

在下一个确认(ACK)封包收到前,将资料排进缓冲区伫列 

  else 

( MSS=最大segment大小)

为什么要同时介绍这两个知识呢?

因为这两个技术同时使用的话会出现问题,下面来看一下问题的出现场景:

A 和B进行数据传输 :  A运行Nagle算法,B运行delayed ACK算法 

1. A->B 发一个packet(数据包), B不回应,delay ACK

2. A-> 再发一个packet(数据包)

3. B收到第二个packet(数据包),这时候会回应第一个packet(数据包),即第一个ACK

4. 假设这时候A里的数据已经<MSS,则A将停止发送数据,等待第二个packet(数据包)的ACK 

此时问题就来了,因为A没有收到第二个packet的ACK确认,同时数据<MSS,由Nagle算法可以得知,这段数据将被被存到缓冲区等待发送,同时这时候B也在等A再发一个packet然后再回应一个ACK,所以这样A和B就发生了死锁了,但是Delayed Ack是有等待机制的,就是会等待500ms,一般是200ms,如果在这200ms内有数据数据要发送(ACK),就回应一个packet(数据包)的ACK,这样就会打破这种死锁的问题。即 只有当200ms(或小于200ms)的延迟过后双方才会继续传输。

当然我们从上面可以看到这种等待机制还是有副作用的,那就是需要等待:一项数据表明:

在以太网上,传输100000字节仅需1ms,但由于delayed ack和nagle的作用却要花费201ms,这显然对程序的效率产生了很大影响. 

对于这个问题的解决方案我们将在下一篇文章中给出解答,一定要继续关注呀! 

作者:Pengcheng Zeng

链接:https://www.zhihu.com/question/42308970/answer/123620051

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

CP/IP协议中针对TCP默认开启了Nagle算法。Nagle算法通过减少需要传输的数据包,来优化网络。关于Nagle算法,@郭无心
同学的答案已经说了不少了。在内核实现中,数据包的发送和接受会先做缓存,分别对应于写缓存和读缓存。

那么针对题主的问题,我们来分析一下。

启动TCP_NODELAY,就意味着禁用了Nagle算法,允许小包的发送。对于延时敏感型,同时数据传输量比较小的应用,开启TCP_NODELAY选项无疑是一个正确的选择。比如,对于SSH会话,用户在远程敲击键盘发出指令的速度相对于网络带宽能力来说,绝对不是在一个量级上的,所以数据传输非常少;而又要求用户的输入能够及时获得返回,有较低的延时。如果开启了Nagle算法,就很可能出现频繁的延时,导致用户体验极差。当然,你也可以选择在应用层进行buffer,比如使用java中的buffered stream,尽可能地将大包写入到内核的写缓存进行发送;vectored
I/O(writev接口)也是个不错的选择。

对于关闭TCP_NODELAY,则是应用了Nagle算法。数据只有在写缓存中累积到一定量之后,才会被发送出去,这样明显提高了网络利用率(实际传输数据payload与协议头的比例大大提高)。但是这由不可避免地增加了延时;与TCP delayed ack这个特性结合,这个问题会更加显著,延时基本在40ms左右。当然这个问题只有在连续进行两次写操作的时候,才会暴露出来。

我们看一下摘自Wikipedia的Nagle算法的伪码实现:

if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if


通过这段伪码,很容易发现连续两次写操作出现问题的原因。而对于读-写-读-写这种模式下的操作,关闭TCP_NODELAY并不会有太大问题。
The user-level solution is to avoid write-write-read sequences on sockets. write-read-write-read is fine. write-write-write is fine. But write-write-read is a killer. So, if you can, buffer up your little writes to TCP and send them all at once.
Using the standard UNIX I/O package and flushing write before each read usually works.
连续进行多次对小数据包的写操作,然后进行读操作,本身就不是一个好的网络编程模式;在应用层就应该进行优化。

对于既要求低延时,又有大量小数据传输,还同时想提高网络利用率的应用,大概只能用UDP自己在应用层来实现可靠性保证了。好像企鹅家就是这么干的。

参考资料:

1.
神秘的40毫秒延迟与 TCP_NODELAY

2.
The Caveats of TCP_NODELAY
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: