在使用一些协议通讯的时候,比如Telnet,会有一个字节字节的发送的情景,每次发送一个字节的有用数据,就会产生41个字节长的分组,20个字节的IP Header 和 20个字节的TCP Header,这就导致了1个字节的有用信息要浪费掉40个字节的头部信息,这是一笔巨大的字节开销,而且这种Small packet在广域网上会增加拥塞的出现。
如果解决这种问题? Nagle就提出了一种通过减少需要通过网络发送包的数量来提高TCP/IP传输的效率,这就是Nagle算法
Nagle算法
Nagle算法主要是避免发送小的数据包,要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的小分组,并在确认到来时以一个分组的方式发出去。
- 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
从上述算法中看出:
1. 对于MSS的片段直接发送
2. 如果有没有被确认的data在缓冲区内,先将待发送的数据放到buffer中直到被发送的数据被确认【最多只能有一个未被确认的小分组】
3. 两种情况置位,就直接发送数据,实际上如果小包,但是没有未被确认的分组,就直接发送数据。
这里通过一个实验来看下Nagle算法对于发送的优化:
实验要求:Client端每次发送1个字节,将hello发送到Server端,然后server再全部发送给Client,其实要点在于Client的发送,预期的结果是:
1. 我们虽然一个字节一个字节的发,但是在协议中使用Nagle算法,可能会有延时等待的状况,即将几个字符合成一个片段进行发送
2. 必须是收到对方的确认之后,才能再次发送
看下实验的结果:
从图中的结果可以看出
1. HELLO 被分成 2个包发送了,应用层调用send 5次,由于Nagle算法,将ELLO合成一个包发送,这样大可以减少Samll packet的数量,增加TCP传输的效率
2. 分成的2个数据包,并没有连续被发出,这也符合Nagle算法的原则,即TCP连接上最多只能有一个未被确认的小分组,等待收到ACK之后,才发第二个封包。
禁用Nagle算法
在默认的情况下,Nagle算法是默认开启的,Nagle算法比较适用于发送方发送大批量的小数据,并且接收方作出及时回应的场合,这样可以降低包的传输个数。同时协议也要求提供一个方法给上层来禁止掉Nagle算法
当你的应用不是连续请求+应答的模型的时候,而是需要实时的单项的发送数据并及时获取响应,这种case就明显不太适合Nagle算法,明显有delay的。
linux提供了TCP_NODELAY的选项来禁用Nagle算法。
禁用方法:
- setsockopt(client_fd, SOL_TCP, TCP_NODELAY,(int[]){1}, sizeof(int));
来看下禁用后同样发送Hello的实验结果
从实验结果中可以得出如下结论:
1. 禁止Nagle算法,每一次send,都会组一个包进行发送,HELLO被分成5个小包分别发送
2.不用等待ACK,可以连续发送
Delay ACK and Nagle
Nagle指出Nagle算法与Delay ACK机制有共存的情况下会有一些非常糟糕的状况,比如举一个场景:PC1和PC2进行通信,PC1发数据给PC2,PC1使用Nagle算法,PC2有delay ACK机制
1. PC1发送一个数据包给PC2,PC2会先不回应,delay ACK
2. PC1再次调用send函数发送小于MSS的数据,这些数据会被保存到Buffer中,等待ACK,才能再次被发送
从上面的描述看,显然已经死锁了,PC1在等待ACK,PC2在delay ACK,那么解锁的代价就是Delay ACK的Timer到期,至少40ms[40ms~500ms不等],也就是2种算法在通信的时候,会产生不必要的延时!
可以看下实验的图示, 9包是发送H字符到server,可以看到隔了30ms的delay ack延时才等到数据,发送一个DATA+ACK包,在这个时间段内其实也是用包发送的,但是nagle算法是要等待ACK的到来才能发包的,所以也会看到11号包要在ACK包之后。如果30ms延时仍然没有数据,就是我们上述说的那样白白的等待一个delay ack超时。
如何来解决这种问题?
其实Nagle算法本身的立意是好的,避免网络充斥着过多的小包,提高网络传输的效率,同时Delay ACK也是为了提高TCP的性能,不过二者遇到了,就比较悲剧了。其实在RFC中已经提供了一个用户级别的解决方案,即避免 write--write--read的这种写法,write--read--write--read 以及write--write--write都是OK的。假设这里有数据要发送,这个数据分为头部和数据部分,2部分发送,然后再回读响应,写法如下
1.write(header)
2.write(body)
3.read(response)
服务器read写法如下
1. read(request)
2. process(request)
3. write(response)
应用程序使能了Nagle算法,第一个header是一定能够发送出去的,因为前面没有带确认的数据,服务器端接收到header之后,也发现是不完全的,还会再次等待request,同时要delay ACK,再次发write的时候,发现没有ACK,也会等待ACK延迟发送。这样只能超时才能再次传输。
这个问题的产生,主要是Nagle和delay ACK 副作用以及write write read的程式造成的。一般写程序的时候不推荐这样的写法。如何解决? 可能想到的就是上面的禁止掉Nagle算法,这也是一种办法,不过这种办法同时也会让网络充斥小包,降低效率。对于应用程序来讲,只要避免write-wirte-read的这种写法就可以避免掉问题,比如write一块去写,一次写成功,就一个包发过去了,就没有等待delay的过程了!所以写程序的时候还是要注意再注意。