tcp的发送端一个小包就能打破对端的delay_ack么?
3.10內(nèi)核,反向合入4.9的bbr。
最近分析bbr的時(shí)候,收集了線上的一些報(bào)文,其中有一個(gè)疑問(wèn)一直在我腦海里面,如下:
本身處于delay_ack狀態(tài)的客戶端,大概40ms回復(fù)一個(gè)delay_ack,當(dāng)收到一個(gè)490字節(jié)的小包之后,立刻回復(fù)了ack。且不止出現(xiàn),是有規(guī)律的出現(xiàn):
我是如何確定這個(gè)ack一定是打破了delay_ack的呢,除了在時(shí)間上和發(fā)包的時(shí)間相隔很短,我還特意確認(rèn)了一下,之后報(bào)文的ack是否立刻回復(fù)的,結(jié)果確定
都是立刻回復(fù)的,也就是進(jìn)入了quick_ack的模式,回復(fù)快速ack的數(shù)量也剛好是16,因?yàn)?tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);
中,對(duì)shinfo->gso_segs = 1;這樣在減少quick閾值的時(shí)候,每次?tcp_event_ack_sent 只是將quick減去1,這樣就是說(shuō),一旦打破delay_ack,那么至少兩個(gè),
至多16個(gè)quick ack,也就是符合代碼:
icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS);根據(jù)接收窗口和mss,以及
/* Maximal number of ACKs sent quickly to accelerate slow-start. */ #define TCP_MAX_QUICKACKS???16U 我鐵定確定了這個(gè)是打破了原本的delay_ack.它既不屬于亂序報(bào)文,又沒(méi)有out of window,且收包窗口也沒(méi)有變化,也不是收到一個(gè)已經(jīng)被ack過(guò)的報(bào)文, 且亂序隊(duì)列中并沒(méi)有數(shù)據(jù),為啥它就能打破delay_ack呢?你說(shuō)它到底滿足哪一條呢? /** Check if sending an ack is needed.*/ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) {struct tcp_sock *tp = tcp_sk(sk);/* More than one full frame received... */if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&/* ... and right edge of window advances far enough.* (tcp_recvmsg() will send ACK otherwise). Or...*/__tcp_select_window(sk) >= tp->rcv_wnd) ||/* We ACK each frame or... */tcp_in_quickack_mode(sk) ||/* We have out of order data. */(ofo_possible && !RB_EMPTY_ROOT(&tp->out_of_order_queue))) {/* Then ack it now */tcp_send_ack(sk);} else {/* Else, send delayed ack. */tcp_send_delayed_ack(sk);} }?然后我搜索代碼,看什么時(shí)候調(diào)用?tcp_enter_quickack_mode,發(fā)現(xiàn)沒(méi)有收獲,這個(gè)包不滿足條件。
這個(gè)包的神奇之處在哪?走查了delay_ack打破的條件,沒(méi)法理解這個(gè)代碼邏輯,關(guān)于delay_ack的出現(xiàn)場(chǎng)景,在另一篇博客中有描述《https://www.cnblogs.com/10087622blog/p/10315410.html》
我點(diǎn)擊這個(gè)報(bào)文詳細(xì)分析:
發(fā)現(xiàn)它和其他報(bào)文的區(qū)別是,它帶了push標(biāo)志,帶了走查了代碼,也沒(méi)看出來(lái),為啥push標(biāo)志的報(bào)文會(huì)在delay_ack的情況下,能立刻發(fā)送ack。
最后,再回到報(bào)文,看到一點(diǎn), 那就是這個(gè)小包與上一個(gè)ack之間的間隔為230ms左右,直覺(jué)感覺(jué)這個(gè)時(shí)間偏大,然后走查收包的代碼:
/* There is something which you must keep in mind when you analyze the* behavior of the tp->ato delayed ack timeout interval. When a* connection starts up, we want to ack as quickly as possible. The* problem is that "good" TCP's do slow start at the beginning of data* transmission. The means that until we send the first few ACK's the* sender will sit on his end and only queue most of his data, because* he can only send snd_cwnd unacked packets at any given time. For* each ACK we send, he increments snd_cwnd and transmits more of his* queue. -DaveM*/ static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb) {struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);u32 now;inet_csk_schedule_ack(sk);tcp_measure_rcv_mss(sk, skb);tcp_rcv_rtt_measure(tp);now = tcp_time_stamp;if (!icsk->icsk_ack.ato) {/* The _first_ data packet received, initialize* delayed ACK engine.*/tcp_incr_quickack(sk);icsk->icsk_ack.ato = TCP_ATO_MIN;} else {int m = now - icsk->icsk_ack.lrcvtime;if (m <= TCP_ATO_MIN / 2) {/* The fastest case is the first. */icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + TCP_ATO_MIN / 2;} else if (m < icsk->icsk_ack.ato) {icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + m;if (icsk->icsk_ack.ato > icsk->icsk_rto)icsk->icsk_ack.ato = icsk->icsk_rto;} else if (m > icsk->icsk_rto) {---------------------------進(jìn)入這個(gè)流程/* Too long gap. Apparently sender failed to* restart window, so that we send ACKs quickly.*/tcp_incr_quickack(sk);----------------------------------這個(gè)修改了quickack的發(fā)包數(shù)量sk_mem_reclaim(sk);}}icsk->icsk_ack.lrcvtime = now;tcp_ecn_check_ce(tp, skb);if (skb->len >= 128)tcp_grow_window(sk, skb); }正是因?yàn)榘l(fā)包的間隔大于了?icsk->icsk_rto,所以接收端覺(jué)得很長(zhǎng)時(shí)間沒(méi)有收到包了,那么盡快給對(duì)方回復(fù)ack。icsk->icsk_ack.quick 已經(jīng)大于0了。
static bool tcp_in_quickack_mode(struct sock *sk) {const struct inet_connection_sock *icsk = inet_csk(sk);const struct dst_entry *dst = __sk_dst_get(sk);return (dst && dst_metric(dst, RTAX_QUICKACK)) ||(icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong); }那么還需要一個(gè)條件就是,icsk->icsk_ack.pingpong 要為0,才行,否則單獨(dú)增加?icsk->icsk_ack.quick 的值并不能保證立刻回復(fù)ack。
而我們目前這個(gè)流,明顯是一個(gè)單向的發(fā)包流,并不是pingpong模式,所以這個(gè)值肯定為0,那么我們就滿足了?tcp_in_quickack_mode 的條件,
打破了本端的delay_ack模式。
?
總結(jié):
我只是搜索了tcp_enter_quickack_mode 的代碼流程,沒(méi)有注意到?tcp_incr_quickack 的調(diào)用,導(dǎo)致這個(gè)問(wèn)題查了小半天。業(yè)務(wù)不精。
如果連續(xù)兩個(gè)小包,加起來(lái)超過(guò)mss了,則可能會(huì)觸發(fā)對(duì)端在delay_ack模式下立即回復(fù)ack,但是如果一個(gè)小包就打破了對(duì)端的delay_ack,則需要關(guān)注這個(gè)
小包的發(fā)包間隔了。
那么問(wèn)題來(lái)了,為什么會(huì)相隔這么長(zhǎng)時(shí)間發(fā)送小包?后面會(huì)繼續(xù)探討。
?
轉(zhuǎn)載于:https://www.cnblogs.com/10087622blog/p/10423118.html
總結(jié)
以上是生活随笔為你收集整理的tcp的发送端一个小包就能打破对端的delay_ack么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: win7可以安装sqlserver200
- 下一篇: elasticsearch全局analy