是为了减少广域网的小分组数目,从而减小网络拥塞的出现。
该算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组ack到达之前不能发送其他的小分组,tcp需要收集这些少量的分组,并在ack到来时以一个分组的方式发送出去;其中小分组的定义是小于MSS的任何分组。(来之网络)
Cork算中不允许链路中有小包存在,当前发送的报文时小包直接hold住,等待一定时间发送。
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是默认开起的。
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一定会约束报文的发送行为,前面的[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机制决策是否发送报文,否则直接发送报文。
/* 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报文无效。
/* 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),链路中有已发送未应答包的小包,堵住,不让发送。
1. 从上面的2)和 3)可以看出 CORK和 NAGLE的区别,CORK不允许链路中有小包,而NAGLE允许链路只有一个未被应答的小包。
2. 机制实现上CORK优先级高于NAGLE,如果同时设置了CORK和NAGLE,那么CORK生效,NAGLE失效。
CORK 算法的配置也是在do_tcp_setsockopt实现的,不在分析了。
1. NAGLE或者CORK开启后,数据被延长发送了,那么下次发送的触发逻辑有那些?分别需要delay多长时间发送 ?
2. NAGLE或者CORK在什么业务上或者场景下开始是有利的 ?