TCP的数据流可以大致分为两类:交互数据流和成块数据流。交互数据流通常很小,而成块数据流通常是满长度的(512字节)。TCP在处理两类数据的时候,其算法有所不同。
数据捎带ACK
对于交互式输入,一种可能的按键回显方法如下:
图中,中间的数据字节的确认和数据字节的回显可以合并发送,即通过经受时延的确认(数据捎带ACK)来合并。
数据捎带ACK:TCP在收到数据的时候并不立即发送ACK,而是推迟发送,以便将ACK与需要沿该方向发送的数据一起发送,通常的实现时延为200ms。
Nagle算法
前面可以看到,TCP交互的双方每次发送数据的时候(即便是只有一个字节的数据),都需要产生一个(数据长度+40字节)的分组。当数据的长度远小于40字节时,网络的实际利用率其实很低,并且大量的小分组也会增加拥塞的可能。
Nagle算法正是解决了该问题。它要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。TCP收集这些小的分组,并在确认到来时以一个分组的形式发出去。其特点是:确认到达的越快,数据也就发送的越快,并可以发送更少的分组。
TCP链接的过程中,默认开启Nagle算法,进行小包发送的优化。优化网络传输,兼顾网络延时和网络拥塞。
Nagle虽然解决了小封包问题,但也导致了较高的不可预测的延迟,同时降低了吞吐量。这个时候可以置位TCP_NODELAY关闭 Nagle算法,有数据包的话直接发送保证网络时效性。
在进行大量数据发送的时候可以置位TCP_CORK关闭Nagle算法保证网络利用性。尽可能的进行数据的组包,以最大mtu传输,如果发送的数据包大小过小则如果在0.6~0.8S范围内都没能组装成一个MTU时,直接发送。如果发送的数据包大小足 够间隔在0.45内时,每次组装一个MTU进行发送。如果间隔大于0.4~0.8S则,每过来一个数据包就直接发送。
TCP_NODELAY 选项
默认情况下,发送数据采用Negale 算法。这样虽然提高了网络吞吐量,但是实时性却降低了,在一些交互性很强的应用程序来说是不允许的,使用TCP_NODELAY选项可以禁止Negale 算法。
此时,应用程序向内核递交的每个数据包都会立即发送出去。需要注意的是,虽然禁止了Negale 算法,但网络的传输仍然受到TCP确认延迟机制的影响。
TCP_CORK 选项
所谓的CORK就是塞子的意思,形象地理解就是用CORK将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去。设置该选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,当然若一定时间后(一般为200ms,该值尚待确认),内核仍然没有组合成一个MTU时也必须发送现有的数据(不可能让数据一直等待吧)。
然而,TCP_CORK的实现可能并不像你想象的那么完美,CORK并不会将连接完全塞住。内核其实并不知道应用层到底什么时候会发送第二批数据用于和第一批数据拼接以达到MTU的大小,因此内核会给出一个时间限制,在该时间内没有拼接成一个大包(努力接近MTU)的话,内核就会无条件发送。也就是说若应用层程序发送小包数据的间隔不够短时,TCP_CORK就没有一点作用,反而失去了数据的实时性(每个小包数据都会延时一定时间再发送)。
Nagle算法与CORK算法区别
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。如此看来这二者在避免发送小包上是一致的,在用户控制的层面上,Nagle算法完全不受用户socket的控制,你只能简单的设置TCP_NODELAY而禁用它,CORK算法同样也是通过设置或者清除TCP_CORK使能或者禁用之,然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下(很重要,否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。
参考: