如果一个 TCP 连接的一端启用了 Nagle‘s Algorithm,而另一端启用了 TCP Delayed Ack,而发送的数据包又比较小,则可能会出现这样的情况:发送端在等 待接收端对上一个packet 的 Ack 才发送当前的 packet,而接收端则正好延迟了 此 Ack 的发送,那么这个正要被发送的 packet 就会同样被延迟。当然 Delayed Ack 是有个超时机制的,而默认的超时正好就是40ms。
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里的数据已经
此时问题就来了,因为A没有收到第二个packet的ACK确认,同时数据
当然我们从上面可以看到这种等待机制还是有副作用的,那就是需要等待:一项数据表明:
在以太网上,传输100000字节仅需1ms,但由于delayed ack和nagle的作用却要花费201ms,这显然对程序的效率产生了很大影响.
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。
所谓“小段”,指的是小于MSS尺寸的数据块,
所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
举个例子,比如之前的blog中的实验,一开始client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,接着,client端又调用write操作写入‘/r/n’(简称B块),这个时候,A块的ACK没有返回,所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后),B块才被发送。整个过程如图所示:
这里还隐藏了一个问题,就是A块数据的ACK为什么40ms之后才收到?这是因为TCP/IP中不仅仅有nagle算法,还有一个ACK延迟机制。当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。在我之前的时间中,t大概就是40ms。这就解释了为什么'/r/n'(B块)总是在A块之后40ms才发出。
如果你觉着nagle算法太捣乱了,那么可以通过设置TCP_NODELAY将其禁用。当然,更合理的方案还是应该使用一次大数据的写操作,而不是多次小数据的写操作。