TCP_NODELAY 和 TCP_CORK

Nagle算法

是为了减少广域网的小分组数目,从而减小网络拥塞的出现。

该算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组ack到达之前不能发送其他的小分组,tcp需要收集这些少量的分组,并在ack到来时以一个分组的方式发送出去;其中小分组的定义是小于MSS的任何分组。(来之网络)

Cork算法

Cork算中不允许链路中有小包存在,当前发送的报文时小包直接hold住,等待一定时间发送。

Nagle算法开启

TCP_NODELAY支持针对sock设置,具体代码在如下函数中:

static int do_tcp_setsockopt(struct sock *sk, int level,
                int optname, char __user *optval, unsigned int optlen)

其中  optval 非0表示,关闭TCP_NODELAY,否则表示打开 ,TCP_NODELAY是默认开起的。

Nagle开启代码片段

        case TCP_NODELAY:
                if (val) {
                        /* TCP_NODELAY is weaker than TCP_CORK, so that
                         * this option on corked socket is remembered, but
                         * it is not activated until cork is cleared.
                         *
                         * However, when TCP_NODELAY is set we make
                         * an explicit push, which overrides even TCP_CORK
                         * for currently queued segments.
                         */
                        tp->nonagle |= TCP_NAGLE_OFF|TCP_NAGLE_PUSH;
                        tcp_push_pending_frames(sk);
                } else {
                        tp->nonagle &= ~TCP_NAGLE_OFF;
                }
                break;

1. TCP_NODELAY功能弱于TCP_CORK,如果在TCP_CORK的连接上设置了TCP_NODELAY,实际上不会生效,必须等到TCP_CORK功能关闭。

2. TCP_NODELAY在配置(也即是触发调用do_tcp_setsockopt函数)关闭是,会触发一次PSH标志的报文发送,这个机制不收TCP_CORK控制。

TCP_NODELAY报文发送关系

很明显TCP_NODELAY一定会约束报文的发送行为,前面的[Nagle算法]已经交代了TCP连接关联NAGLE的发送行为,下面我们交单看看代码。

发送路径上关联的函数如下:

static inline void tcp_push_pending_frames(struct sock *sk)

开始引入了nonagle因素了,上面do_tcp_setsockopt函数中设置的变量标志: tp->nonagle


void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
                               int nonagle) 

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
                           int push_one, gfp_t gfp)

在tcp_write_ximit中的代码片段如下

                if (tso_segs == 1) {
                        if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
                                                     (tcp_skb_is_last(sk, skb) ?
                                                      nonagle : TCP_NAGLE_PUSH))))
                                break;
                } else {
                        if (!push_one &&
                            tcp_tso_should_defer(sk, skb, &is_cwnd_limited,
                                                 &is_rwnd_limited, max_segs))
                                break;
                }

tcp_nagle_test 就是TCP_NODELAY的校验函数,报文的发送与否决策再此。

第四个参数是(最后一个)是nagle控制变量,如果当前发送队列只有一个报文(考虑TSO,发送队列最后一个报文),则交给TCP_NODELAY机制决策是否发送报文,否则直接发送报文。

具体的nagle判断行为

/* Return true if the Nagle test allows this packet to be
 * sent now.
 */
static inline bool tcp_nagle_test(const struct tcp_sock *tp, const 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.
         */
        if (nonagle & TCP_NAGLE_PUSH)
                return true;

        /* Don't use the nagle rule for urgent data (or for the final FIN). */
        if (tcp_urg_mode(tp) || (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN))
                return true;

        if (!tcp_nagle_check(skb->len < cur_mss, tp, nonagle))
                return true;

        return false;
}

1. 如果打上了TCP_NAGLE_PUSH标志,返回true,外层逻辑是直接发送报文。

2. 如果报文时FIN报文或urg报文直接发送,也即TCP_NODELAY对FIN报文和URG报文无效。

TCP_NODELAY主判定逻辑

/* Return false, if packet can be sent now without violation Nagle's rules:
 * 1. It is full sized. (provided by caller in %partial bool)
 * 2. Or it contains FIN. (already checked by caller)
 * 3. Or TCP_CORK is not set, and TCP_NODELAY is 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 bool tcp_nagle_check(bool partial, const struct tcp_sock *tp,
                            int nonagle)
{
        return partial &&
                ((nonagle & TCP_NAGLE_CORK) ||
                 (!nonagle && tp->packets_out && tcp_minshall_check(tp)));
}


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

tcp_nagle_check解读:

1. 参数1- partial,小包为true,大包false。

2. 参数3- nonagle,最上面do_tcp_setsockopt设置的参数。

3. 主判定逻辑:

1)很明显大包返回false,tcp_nagle_test函数返回true,在tcp_write_xmit中继续走发送的剩余流程,也即TCP_NONAGLE对大包的发送不做干预。

2)如果该sock开启了TCP_NAGLE_CORK,直接堵住,不让发送。

3)如果nagle为0(表示默认开启NAGLE),链路中有已发送未应答包的小包,堵住,不让发送。

CORK和NAGLE的区别

1. 从上面的2)和 3)可以看出 CORK和 NAGLE的区别,CORK不允许链路中有小包,而NAGLE允许链路只有一个未被应答的小包。

2. 机制实现上CORK优先级高于NAGLE,如果同时设置了CORK和NAGLE,那么CORK生效,NAGLE失效。

CORK 算法的配置也是在do_tcp_setsockopt实现的,不在分析了。

遗留2个问题

1. NAGLE或者CORK开启后,数据被延长发送了,那么下次发送的触发逻辑有那些?分别需要delay多长时间发送 ?

2. NAGLE或者CORK在什么业务上或者场景下开始是有利的 ? 

上一篇:[国嵌笔记][025][ARM指令分类学习]


下一篇:TCP为什么会出现粘包现象