tcp连接在断网后的恢复能力(2)
1470 * Minshall version sounds: there are no _small_
1471 * segments in flight. (tcp_nagle_check)
1472 * c) We have too many packets 'in flight'
1473 *
1474 * Don't use the nagle rule for urgent data (or
1475 * for the final FIN -DaveM).
1476 *
1477 * Also, Nagle rule does not apply to frames, which
1478 * sit in the middle of queue (they have no chances
1479 * to get new data) and if room at tail of skb is
1480 * not enough to save something seriously (<32 for now).
1481 */
1482
1483 /* Don't be strict about the congestion window for the
1484 * final FIN frame. -DaveM
1485 */
1486 return (((nonagle&TCP_NAGLE_PUSH) || tp->urg_mode
1487 || !tcp_nagle_check(tp, skb, cur_mss, nonagle)) &&
1488 (((tcp_packets_in_flight(tp) + (pkts-1)) < tp->snd_cwnd) ||
1489 (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN)) &&
1490 !after(TCP_SKB_CB(skb)->end_seq, tp->snd_una + tp->snd_wnd));
1491 }
这个函数的注释比实现代码还多,return后面复杂的条件判断可以被拆开:其实是三个条件的“and”操作,我们看第二个条件,就是:
(((tcp_packets_in_flight(tp) + (pkts-1)) < tp->snd_cwnd) || (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN))
其中,tcp_packets_in_flight是指正在“飞行中”的packet数,也就是正在网络上的包数,它的计算方法是:
发送过一次的包数 + 重发过的包数 - 队列中存留的包数
而TCPCB_FLAG_FIN是指一端是否发完了数据,这个在我们的项目中不存在,我们的数据没个完。
这下清楚了:
如果设了NODELAY,则关闭了nagle算法,大量的小数据包被发出去(看看上面第一个tcpdump的数据),在突然断网时,in_flight的包很多,多得超过了snd_cwnd即发送窗口的大小,于是tcp_snd_test返回0,真正的发送没有发生。不发送存着的数据,snd_buf中的空间就腾不出来,tcp_sendmsg就一直返回0。恶性循环。
有人要问了,既然snd_buf没空间了,那ACK又是怎么发出去的呢?答案是:发ACK不需要snd_buf空间,它直接就扔出去了。在socket收消息时,会调用tcp_recvmsg,收完后会清空读缓冲cleanup_rbuf,cleanup_rbug里会发送ACK消息,如图:
tcp_write_xmit里的操作其实就是从发送队列里循环拿skb,然后调用tcp_transmit_skb发到网络上去,而ACK是直接就调用tcp_transmit_skb,故而不经过发送队列,也就不受snd_buf空间的影响。
还有人可能问,这岂不是linux tcp协议栈的bug?我觉得有可能,因为在linux的2.6.32的内核里,tcp_snd_test函数已经没有了(实际上从2.6.13开始tcp_snd_test就没了,用rhel5的人可以松口气了),__tcp_push_pending_frame里那个别扭的“||”操作也拿掉了,改为直接调用tcp_write_xmit,再在tcp_write_xmit里对窗口和nagle算法就行判断,决定是否该发送包。逻辑更清晰,bug也避开了。
- 最新评论
