参考tcp(7): TCP protocol
TCP_NODELAY
If set, disable the Nagle algorithm. This means that segments are always sent as soon as possible, even if there is only a small amount of data. When not set, data is buffered until there is a sufficient amount to send out, thereby avoiding the frequent sending of small packets, which results in poor utilization of the network. This option is overridden by TCP_CORK; however, setting this option forces an explicit flush of pending output, even if TCP_CORK is currently set.TCP/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