TCP Nagle剖析

本文内容:TCP Nalge的原理、内核实现以及应用。

作者:zhangskd @ csdn

 

概述

 

Nagle's algorithm, named after John Nagle, is a means of improving the efficiency of TCP/IP networks
by reducing the number of packets that need to be sent over the network.
Nagle's algorithm works by combining a number of small outgoing messages, and sending them
all at once. Specifically, as long as there is a sent packet for which the sender has received no
acknowledgment, the sender should keep buffering its output until it has a full packet's worth of output,
so that output can be sent all at once.
Nagle's algorithm purposefully delays transmission, increasing bandwidth efficiency at the expense of latency. [1]
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 sill in the pipe
            enqueue data in the buffer until an acknowledge is received
        else
            send data immediately
        end if
    end if
end if 

看起来很简单,但实际实现比这个复杂得多,下面会具体分析。

 

选项

 

(1)TCP_NODELAY

默认情况下,发送数据采用Nagle算法。Nagle算法适用于发送方需要发送大批量的数据,
并且接收方会及时作出回应的场合,这种算法通过减少传输数据的次数来提高通信效率。
TCP_NODELAY用于禁用Nagle。

(2)TCP_CORK

Nagle组织包的长度是由系统决定的,有时候我们知道我们每分钟产生1字节,共1000字节。
如果完全由Nagle算法来发送的话,可能还是会1字节1字节的发送,如果往返时延小于1分钟。
这个时候先设置TCP_CORK能够尽量阻塞住TCP,等我们write完1000字节之后,取消TCP_CORK
这个时候就能够将1000字节一次发出。

TCP_NODELAY和TCP_CORK都是禁用Nagle算法的,只不过TCP_NODELAY完全关闭而
TCP_CORK完全由自己决定发送时机。因为它们是处理一件事的两种不同方法,所以不能同时使用。

 

实现

struct tcp_sock {
    ...
    u32 snd_wnd; /* receiver's advertised window */
    u32 snd_up; /* urgent pointer */
    u32 snd_sml; /* Last byte of the most recently transmitted small packet */
    u8 nonagle : 4  /* Disable Nagle algorithm */
    ...
};
/* Flags in tp->nonagle */
#define TCP_NAGLE_OFF 1 /* Nagle's algo is disabled, 对应于TCP_NODELAY*/
#define TCP_NAGLE_CORK 2 /* Socket is corked,对应于TCP_CORK*/
#define TCP_NAGLE_PUSH 4 /* Cork is overridden for already queued data,对应于TCP_CORK*/

在tcp_write_xmit()中的Nagle检测:

if(unlikely(! tcp_nagle_test(tp, skb, mss_now, (tcp_skb_is_last(sk, skb) ? nonagle : TCP_NAGLE_PUSH))))

    break;

注意:Nagle算法只针对发送队列的最后一个数据包,对于发送队列中间的数据包无效,因为只有发送队列最后

一个数据包才有机会获得新的数据,形成更大的包。

/* Return non-zero if the Nagle test allows this packet to be sent now. */
static inline int tcp_nagle_test (struct tcp_sock *tp, struct sk_buff *skb, unsigned int cur_mss, int nonagle)
{
    /* Nagle rule does not apply to frames, which sit in the middle of the write_queue (they have no chances
     * to get new data).
     * This is implemented in the callers, where they modify the 'nonagle' argument based upon the location
     * of SKB in the send queue.
     * 此次注释解释了本函数调用处中对nonagle的选择,精彩!*/
     */
    if (nonagle & TCP_NAGLE_PUSH)
        return 1;

    /* Don't use the nagle rule for urgent data (or for the final FIN).
     * Nagle can be ignored during F-RTO too (see RFC4138).
     */
     if (tcp_urg_mode(tp) || (tp->frto_counter == 2) || 
        (TCP_SKB_CB(skb)->flags & TCPHDR_FIN))
        return 1;
     
    if(!tcp_nagle_check(tp, skb, cur_mss, nonagle))
        return 1;

    return 0;
}

static inline int tcp_urg_mode (const struct tcp_sock * tp)
{
    /* snd_up: urgent pointer */
    return tp->snd_una != tp->snd_up;
}
 
/* Return 0, if packet can be sent now without violation Nagle's rules:
 * 1. It is full sized.
 * 2. Or it contains FIN. (already checked by caller)
 * 3. Or TCP_NODELAY was set.
 * 4. Or TCP_CORK is not set, and all sent packets are ACKed.
 *      With Minshall's modification: all sent small packets are ACKed.
 */
static inline int tcp_nagle_check (const struct tcp_sock *tp, const struct sk_buff *skb,
                                   unsigned mss_now, int nonagle)

{
    return skb->len < mss_now && ((nonalge & TCP_NAGLE_CORK) || 
                (! nonalge && tp->packets_out && tcp_minshall_check(tp)));
}

/* Minshall's variant of the Nagle send check. */
static inline int tcp_minshall_check (const struct tcp_sock *tp)
{
    return after(tp->snd_sml, tp->snd_una) && !after(tp->snd_sml, tp->snd_nxt);
}

 可以马上发送,不使用Nagle的情况

1. 此skb不是发送队列的最后一个包。对于发送队列中间的包,使用Nagle也没用,因为它们无法再获得新的数据。

2. 发送的是紧急数据(urgent data)。顾名思义,紧急数据应该马上发送的,怎么能被耽误呢?

3. 处于F-RTO中。此阶段会发送两个小探测包探测超时是否为虚假的,此时不能够被耽误,所以禁用Nagle。

4. 数据包包含结束标志(TCPHDR_FIN)。表明发送即将结束,再用Nagle延迟就多此一举了。

5. 数据包满负荷。既然都已经满负荷了,还等什么。

6. 没设置TCP_CORK (TCP_NAGLE_CORK,2),设置了TCP_NODELAY (TCP_NAGLE_OFF,1)。

7. 没有设置TCP_CORK,且网络中不存在小包。

 

反过来看,可以使用Nagle时需符合以下条件

1. 此skb是发送队列的最后一个包。

2. 发送的不是紧急数据。

3. 不处于F-RTO中。

4. 数据包不含FIN标志。

5. 数据包大小小于mss_now,不是满负荷的。

6. 设置了TCP_NAGLE_CORK,或者,

     没有设置TCP_NODELAY,且网络中存在小包。

 

可以看到,虽然内核默认使用Nalge,但使用的条件还是比较苛刻的,所以Nagle一般是unlikely的。

 

应用

 

由于Nagle会造成延迟的增加,对于一些实时应用,我们需要禁用Nagle。

(1)  TCP_NODELAY

禁用Nagle

int one = 1;

setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));

取消禁用

int zero = 0;

setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));

一旦禁用Nagle,任何大小的数据包都会被马上发送,发送时机由系统决定。

 

(2) TCP_CORK

This option tells TCP to wait for the application to remove the cork before sending any packets.

禁止发送小包,用塞子塞住

int one = 1;

setsockopt(descriptor, SOL_TCP, TCP_CORK, &one, sizeof(one));

积累得差不多了,拔出塞子,允许发送

int zero = 0;

setsockopt(descriptor, SOL_TCP, TCP_CORK, &zero, sizeof(zero));

可以看到,和TCP_NODELAY选项不同,TCP_CORK允许我们自己决定何时阻塞何时发送。

 

Reference

 

[1] http://en.wikipedia.org/wiki/Nagle's_algorithm 维基百科,概括的非常好

[2] http://blog.csdn.net/dog250/article/details/5941637 TCP_NODELAY和TCP_CORK的区别

[3] TCP_NODELAY和TCP_CORK的使用

 

posted on 2012-10-12 15:31  张大大123  阅读(247)  评论(0编辑  收藏  举报

导航