当发送端应用进程产生数据很慢(比如Telnet应用),就会使应用进程间传送的TCP有效载荷很小(可能只有1个字节),而传输开销有40字节(20字节的IP头+20字节的TCP头) 这种现象就叫糊涂窗口综合症(Silly Windw Syndrome)。如果有大量的小TCP报文在网络中传输会导致网络拥塞。Nagle算法是为了解决TCP数据发送端“糊涂窗口综合症”而产生的。
Nagle算法会对发送缓冲区内的一定数量的消息进行自动连接(该处理过程称为Nagling),通过减少必须发送的封包的数量,提高了网络应用程序系统的效率,减缓了网络拥塞。所谓的CORK就是塞子的意思,形象地理解就是用CORK将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去。设置该选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去。
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。但这二者都会避免发送小包,在这一点上是一致的。而且在Linux的实现上,Nagle和CORK也是结合在一起的。然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下(否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。
由前文可知,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->packets_out && tcp_minshall_check(tp))); 1462 } 1463 1464 /* Return true if the Nagle test allows this packet to be 1465 * sent now. 1466 */ 1467 static inline bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb, 1468 unsigned int cur_mss, int nonagle) 1469 { 1470 /* Nagle rule does not apply to frames, which sit in the middle of the 1471 * write_queue (they have no chances to get new data). 1472 * 1473 * This is implemented in the callers, where they modify the 'nonagle' 1474 * argument based upon the location of SKB in the send queue. 1475 */ 1476 if (nonagle & TCP_NAGLE_PUSH) //有TCP_NAGLE_PUSH标记 1477 return true; //允许发送 1478 1479 /* Don't use the nagle rule for urgent data (or for the final FIN). */ 1480 if (tcp_urg_mode(tp) || (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)) 1481 return true; //放行 1482 1483 if (!tcp_nagle_check(tp, skb, cur_mss, nonagle)) 1484 return true; //通过 1485 1486 return false; //不许发送 1487 }
1461:tp->packets_out非0意味着有报文发送出去但未被确认;tcp_minshall_check为假意味着snd_msl <= snd_una(snd_msl不可能大于snd_nxt),即发送的最新的小报文段已经被确认。tp->packets_out && tcp_minshall_check(tp)为真的含义是有发送出去但尚未被确认的小报文。nonagle的类型有:
214 #define TCP_NAGLE_OFF 1 /* Nagle's algo is disabled */ 215 #define TCP_NAGLE_CORK 2 /* Socket is corked */ 216 #define TCP_NAGLE_PUSH 4 /* Cork is overridden for already queued data */
从代码上看,Nagle算法允许发送数据的条件为满足下列标准中的任意一个即可:
(1)nonagle设置了TCP_NAGLE_PUSH标记
(2)TCP处于紧急模式(有紧急数据要处理)
(3)报文中有FIN标记
(4)tcp_nagle_check为假
先来看(4)。tcp_nagle_check为假即1459-1461行的逻辑为假成立的条件为下列标准中的任意一个:
1)数据长度大于等于MSS
2)nonagle没有设置TCP_NAGLE_CORK标记并且nonagle有TCP_NAGLE_OFF标记(即nonagel == TCP_NAGLE_OFF为真)
3)nonagle == 0为真且没有未确认的小报文
用这3个条件替换(4),我们得到了新的Nagle算法允许发送数据的条件:
(1)nonagle设置了TCP_NAGLE_PUSH标记
(2)TCP处于紧急模式(有紧急数据要处理)
(3)报文中有FIN标记
(4)数据长度大于等于MSS
(5)nonagle == TCP_NAGLE_OFF为真
(6)nonagle == 0为真且没有未确认的小报文
再来看(1)满足的情况。nonagle是由tcp_write_xmit函数的nonagle参数传入。tcp_sendmsg函数中对tcp_write_xmit函数的调用是用过tcp_push系列函数完成的:
1016 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, 1017 size_t size) 1018 { ... 1099 skb = tcp_write_queue_tail(sk); //得到队列尾部的skb 1100 if (tcp_send_head(sk)) { //skb尚未发送 1101 if (skb->ip_summed == CHECKSUM_NONE) 1102 max = mss_now; 1103 copy = max - skb->len; 1104 } 1105 1106 if (copy <= 0) { //空间不足 1107 new_segment: 1108 /* Allocate new segment. If the interface is SG, 1109 * allocate skb fitting to single page. 1110 */ 1111 if (!sk_stream_memory_free(sk)) 1112 goto wait_for_sndbuf; 1113 1114 skb = sk_stream_alloc_skb(sk, 1115 select_size(sk, sg), 1116 sk->sk_allocation); //申请一个新的 1117 if (!skb) 1118 goto wait_for_memory; ... 1201 if (skb->len < max || (flags & MSG_OOB) || unlikely(tp->repair)) 1202 continue; 1203 //数据长度小于MSS的skb不会走到这里 1204 if (forced_push(tp)) { //积累一定字节数的报文后forced_push会为真 1205 tcp_mark_push(tp, skb); 1206 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);//传给tcp_write_xmit函数TCP_NAGLE_PUSH标记 1207 } else if (skb == tcp_send_head(sk)) //发送的是新写入内核的第一个包 1208 tcp_push_one(sk, mss_now); //传给tcp_write_xmit函数TCP_NAGLE_PUSH标记 1209 continue; 1210 1211 wait_for_sndbuf: 1212 set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); 1213 wait_for_memory: //无法申请内核内存或接收缓存空间达到限制 1214 if (copied) 1215 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);//传给tcp_write_xmit函数TCP_NAGLE_PUSH标记 ... 1223 1224 out: 1225 if (copied) 1226 tcp_push(sk, flags, mss_now, tp->nonagle); //不传递TCP_NAGLE_PUSH标记给tcp_write_xmit函数 ...1099-1116:数据会优先放在有剩余空间且尚未发送的旧skb中,这样就可以将多个小数据段拼成一个大的发送
1204-1206:forced_push的功能是检查是否允许向外推送报文:
585 static inline void tcp_mark_push(struct tcp_sock *tp, struct sk_buff *skb) 586 { 587 TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH; 588 tp->pushed_seq = tp->write_seq; 589 } 590 591 static inline bool forced_push(const struct tcp_sock *tp) 592 { 593 return after(tp->write_seq, tp->pushed_seq + (tp->max_window >> 1)); 594 }可见当有多于tp->max_window >> 1个字节的数据没有被"mark_push"时,forced_push为真。
1207:当前写入数据的skb为要发送的首包时这个条件为真;tcp_send_head指针的设置是在skb_entail中完成,而必须是新申请的skb才能调用skb_entail来加入发送队列。这就意味着当前skb之前的所有skb已经全部发送完毕。
1213:内存紧张时,需要发送数据以便收到确认时能够腾出发送缓存的空间
在tcp_sendmsg的1026、1028、1215、1226行共有4次发送数据,其中3次会有TCP_NAGLE_PUSH标记,但这时发送的都是满MSS的大包。1226行的调用会使用tp->nonagle中的标记,但由于tcp_sendmsg会调用skb_entail函数去除TCP_NAGLE_PUSH标记:
596 static inline void skb_entail(struct sock *sk, struct sk_buff *skb) 597 { 598 struct tcp_sock *tp = tcp_sk(sk); 599 struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); 600 601 skb->csum = 0; 602 tcb->seq = tcb->end_seq = tp->write_seq; 603 tcb->tcp_flags = TCPHDR_ACK; 604 tcb->sacked = 0; 605 skb_header_release(skb); 606 tcp_add_write_queue_tail(sk, skb); 607 sk->sk_wmem_queued += skb->truesize; 608 sk_mem_charge(sk, skb->truesize); 609 if (tp->nonagle & TCP_NAGLE_PUSH) 610 tp->nonagle &= ~TCP_NAGLE_PUSH; 611 }故1226行不会传递TCP_NAGLE_PUSH标记。1226行会调用tcp_push函数:
619 static inline void tcp_push(struct sock *sk, int flags, int mss_now, 620 int nonagle) 621 { 622 if (tcp_send_head(sk)) { 623 struct tcp_sock *tp = tcp_sk(sk); 624 625 if (!(flags & MSG_MORE) || forced_push(tp)) 626 tcp_mark_push(tp, tcp_write_queue_tail(sk)); 627 628 tcp_mark_urg(tp, flags); 629 __tcp_push_pending_frames(sk, mss_now, 630 (flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle); 631 } 632 }可见当应用进程使用MSG_MORE(可以通过send系统调用的flags参数设置)属性时,tcp_write_xmit会收到TCP_NAGLE_CORK标记。由上面总结的Nagle算法允许发送数据的条件可知,有TCP_NAGLE_CORK标记时Nagle算法是不允许发送数据的。这时的效果与使用setsockopt系统调用的TCP_CORK选项设置TCP_CORK标记的效果是一样的。
除了tcp_sendmsg函数外,设置TCP_NAGLE_PUSH标记的代码还有setsockopt系统调用:
2371 static int do_tcp_setsockopt(struct sock *sk, int level, 2372 int optname, char __user *optval, unsigned int optlen) 2373 { ... 2423 case TCP_NODELAY: 2424 if (val) { 2425 /* TCP_NODELAY is weaker than TCP_CORK, so that 2426 * this option on corked socket is remembered, but 2427 * it is not activated until cork is cleared. 2428 * 2429 * However, when TCP_NODELAY is set we make 2430 * an explicit push, which overrides even TCP_CORK 2431 * for currently queued segments. 2432 */ 2433 tp->nonagle |= TCP_NAGLE_OFF|TCP_NAGLE_PUSH; 2434 tcp_push_pending_frames(sk); //传递tp->nonagle给tcp_write_xmit ... 2503 case TCP_CORK: 2504 /* When set indicates to always queue non-full frames. 2505 * Later the user clears this option and we transmit 2506 * any pending partial frames in the queue. This is 2507 * meant to be used alongside sendfile() to get properly 2508 * filled frames when the user (for example) must write 2509 * out headers with a write() call first and then use 2510 * sendfile to send out the data parts. 2511 * 2512 * TCP_CORK can be set together with TCP_NODELAY and it is 2513 * stronger than TCP_NODELAY. 2514 */ 2515 if (val) { 2516 tp->nonagle |= TCP_NAGLE_CORK; 2517 } else { 2518 tp->nonagle &= ~TCP_NAGLE_CORK; 2519 if (tp->nonagle&TCP_NAGLE_OFF) 2520 tp->nonagle |= TCP_NAGLE_PUSH; 2521 tcp_push_pending_frames(sk); 2522 }这里有两种情况会通过tcp_push_pending_frames传递给tcp_write_xmit函数TCP_NAGLE_PUSH标记来允许发送报文:
1)使用TCP_NODELAY socket选项关闭Nagle算法时(这时条件(5)成立)
2)使用TCP_CORK socket选项"拔出塞子"且Nagle算法已经被关闭时
条件(1)成立等同于上述两个条件任意一个成立。
使条件(5)成立的还有一处,就是TCP丢失探测定时器超时的时候。这个定时器超时的函数是tcp_send_loss_probe:
1974 void tcp_send_loss_probe(struct sock *sk) 1975 { 1976 struct tcp_sock *tp = tcp_sk(sk); 1977 struct sk_buff *skb; 1978 int pcount; 1979 int mss = tcp_current_mss(sk); 1980 int err = -1; 1981 1982 if (tcp_send_head(sk) != NULL) { 1983 err = tcp_write_xmit(sk, mss, TCP_NAGLE_OFF, 2, GFP_ATOMIC); 1984 goto rearm_timer; 1985 }这时会关闭Nagle算法来发送数据。
再看条件(6)。nonagle == 0为真意味着:
1)没有TCP_NAGLE_OFF标记(即没有关闭Nagle算法)
2)没有TCP_NAGLE_CORK标记(即没有开启TCP_CORK算法)
3)没有TCP_NAGLE_PUSH标记(即条件(1)不成立,而1)成立则条件(1)不会成立)
这3个条件同时成立。
下面根据最新的结论更新一下Nagle算法允许发送数据的条件:、
(1)使用TCP_NODELAY socket选项关闭Nagle算法时
(2)TCP处于紧急模式(有紧急数据要处理)
(3)报文中有FIN标记
(4)数据长度大于等于MSS
(5)使用TCP_CORK socket选项"拔出塞子"且Nagle算法已经被关闭时
(6)既没有关闭Nagle算法也没有开启TCP_CORK算法,且没有未确认的小报文
(7)上述条件均未满足但定时器超时
由上述条件可知,在通常情况下(未关闭Nagle算法、发送大量小报文,定时器未超时),Nagle算法会将小报文积攒的发送队列中并拼成一个大的发送,从而减小了小数据的发送。如果对端ACK回复很快的话(条件(6)成立),Nagle算法会认为没有拥塞,就不会拼接太多的数据包,虽然可能不至于导致网络拥塞,网络总体的利用率依然很低。