6.4.1 算法目的
当发送端应用进程产生数据很慢(比如Telnet应用),就会使应用进程间传送的TCP有效载荷很小(可能只有1个字节),而传输开销有40字节(20字节的IP头+20字节的TCP头) 这种现象就叫糊涂窗口综合症(Silly Windw Syndrome)。如果有大量的小TCP报文在网络中传输会导致网络拥塞。Nagle算法是为了解决TCP数据发送端“糊涂窗口综合症”而产生的。
6.4.2 算法原理
Nagle算法会对发送缓冲区内的一定数量的消息进行自动连接(该处理过程称为Nagling),通过减少必须发送的封包的数量,提高了网络应用程序系统的效率,减缓了网络拥塞。所谓的CORK就是塞子的意思,形象地理解就是用CORK将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去。设置该选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去。
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。但这二者都会避免发送小包,在这一点上是一致的。而且在Linux的实现上,Nagle和CORK也是结合在一起的。然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下(否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。
6.4.3 内核实现
由前文可知,TCP在发送数据时会调用tcp_write_xmit函数,而Nagle算法在tcp_write_xmit中设置了一道“关卡”:tcp_nagle_test:
1811 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
1812 int push_one, gfp_t gfp)
1813 {
...
1854 if (tso_segs == 1) { //发送一个报文段
1855 if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
1856 (tcp_skb_is_last(sk, skb) ?
1857 nonagle : TCP_NAGLE_PUSH))))
1858 break; //Nagle算法不允许发送,则停止
...
1893 tcp_minshall_update(tp, mss_now, skb);
...
1893:如果发送的数据长度小于mss_now,则记录最后一个字节的seq到tp->snd_sml中
tcp_nagle_test就像一个闸门,根据Nagle算法的意愿控制着TCP报文的发送。来看看它是怎么做的:
1442 static inline bool tcp_minshall_check(const struct tcp_sock *tp)
1443 {
1444 return after(tp->snd_sml, tp->snd_una) &&
1445 !after(tp->snd_sml, tp->snd_nxt); //snd_una < snd_sml <= snd_nxt
1446 }
1447
1448 /* Return false, if packet can be sent now without violation Nagle's rules:
1449 * 1. It is full sized.
1450 * 2. Or it contains FIN. (already checked by caller)
1451 * 3. Or TCP_CORK is not set, and TCP_NODELAY is set.
1452 * 4. Or TCP_CORK is not set, and all sent packets are ACKed.
1453 * With Minshall's modification: all sent small packets are ACKed.
1454 */
1455 static inline bool tcp_nagle_check(const struct tcp_sock *tp,
1456 const struct sk_buff *skb,
1457 unsigned int mss_now, int nonagle)
1458 {
1459 return skb->len < mss_now && //数据长度小于MSS
1460 ((nonagle & TCP_NAGLE_CORK) || //设置了TCP_NAGLE_CORK标记
1461 (!nonagle && tp-