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在什么业务上或者场景下开始是有利的 ?