TCP的ACK确认系列 — 快速确认
主要內容:TCP的快速確認、TCP_QUICKACK選項的實現(xiàn)。
內核版本:3.15.2
我的博客:http://blog.csdn.net/zhangskd
?
快速確認模式
?
(1) 進入快速確認模式
設置快速確認模式標志,設置在快速確認模式中可以發(fā)送的ACK數(shù)量。
static void tcp_enter_quickack_mode (struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);tcp_incr_quickack(sk); /* 設置在快速確認模式中可以發(fā)送的ACK數(shù)量 */icsk->icsk_ack.pingpong = 0; /* 快速確認模式的標志 */icsk->icsk_ack.ato = TCP_ATO_MIN; /* ACK超時時間 */ }在快速確認模式中,可以發(fā)送的ACK數(shù)量是有限制的,具體額度為icsk->icsk_ack.quick。
所以進入快速確認模式時,需要設置可以快速發(fā)送的ACK數(shù)量,一般允許快速確認半個接收窗口的數(shù)據(jù)量,
但最多不能超過16個,最少為2個。
static void tcp_incr_quickack (struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);/* 可以快速確認半個接收窗口的數(shù)據(jù)量 */unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);if (quickacks == 0)quckacks = 2; /* 最少為2個 */if (quickacks > icsk->icsk_ack.quick)icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS); /* 最多不能超過16個 */ }/* Maximal number of ACKs sent quickly to accelerate slow-start. */ #define TCP_MAX_QUICKACKS 16U?
(2) 檢查是否處于快速確認模式。
如果設置了快速確認標志,且快速確認模式中可以發(fā)送的ACK數(shù)量不為0,就判斷連接處于快速確認模式中,
允許立即發(fā)送ACK。
/* Send ACKs quickly, if "quick" count is not exhausted and the session is not interactive. */ static inline bool tcp_in_quickack_mode (const struct sock *sk) {const struct inet_connectionsock *icsk = inet_csk(sk);/* 如果快速確認模式中可以發(fā)送的ACK數(shù)量不為0,且設置了快速確認標志 */return icsk->icsk_ack.quick && ! icsk->icsk_ack.pingpong; }?
快速ACK的發(fā)送
?
在tcp_rcv_established()中,如果沒有通過TCP的首部預測,就會執(zhí)行慢速路徑來處理接收到的報文。
處理完接收到的報文之后,會調用tcp_data_snd_check()來檢查是否需要發(fā)送數(shù)據(jù),以及是否需要擴大發(fā)送緩存。
然后調用tcp_ack_snd_check()來檢查是否需要發(fā)送ACK,以及是使用快速確認還是延遲確認。
同樣的在通過TCP首部預測的快速路徑中,也會調用__tcp_ack_snd_check()來發(fā)送快速確認或延遲確認。
static inline void tcp_ack_snd_check(struct sock *sk) {/* 如果沒有ACK需要發(fā)送 */if (! inet_csk_ack_scheduled(sk)) {/* We sent a data segment already. */return;}__tcp_ack_snd_check(sk, 1); /* 決定要發(fā)送快速確認還是延遲確認 */ }?
如果此時符合以下任一條件,可以立即發(fā)送ACK,即進行快速確認:
1. 接收緩沖區(qū)中有一個以上的全尺寸數(shù)據(jù)段仍然是NOT ACKed,并且接收窗口變大了。
????所以一般收到了兩個數(shù)據(jù)包后,會發(fā)送ACK,而不是對每個數(shù)據(jù)包都進行確認。
2.? 此時處于快速確認模式中。
3. 亂序隊列不為空。
/* 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);/* 符合以下任一條件,可以立即發(fā)送ACK:* 1. 接收緩沖區(qū)中有一個以上的全尺寸數(shù)據(jù)段仍然是NOT ACKed,并且接收窗口變大了。* 2. 此時處于快速確認模式中。* 3. 亂序隊列不為空。*//* 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 && skb_peek(&tp->out_of_order_queue))) {/* Then ack it now. */tcp_send_ack(sk); /* 立即發(fā)送ACK */} else {/* Else, send delayed ack. */tcp_send_delayed_ack(sk); /* 延遲ACK的發(fā)送,見下一篇blog:) */} }?
ACK的發(fā)送函數(shù)為tcp_send_ack(),如果發(fā)送失敗會啟動ACK延遲定時器。
/* This routine sends an ack and also updates the window. */void tcp_send_ack (struct sock *sk) {struct sk_buff *buff;/* If we have been reset, we may not send again. */if (sk->sk_state == TCP_CLOSE)return;/* We are not putting this on the write queue, so tcp_transmit_skb()* will set the ownership to this sock.*/buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (buff == NULL) { /* 分配skb失敗 */inet_csk_schedule_ack(sk); /* 設置標志位,表示有ACK需要發(fā)送 */inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN; /* 重置ATO *//* 設置延遲確認定時器,超時時間為200ms */inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);return;}/* Reserve space for headers and prepare control bits. */skb_reserve(buff, MAX_TCP_HEADER); /* 設置報文頭部的空間 *//* 初始化不攜帶數(shù)據(jù)的skb的一些控制字段 */tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);/* Send it off, this clears delayed acks for us. */TCP_SKB_CB(buff)->when = tcp_time_stamp;tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC)); }/* 設置標志位,表示有ACK需要發(fā)送。*/ static inline void inet_csk_schedule_ack (struct sock *sk) {inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_SCHED; }/* Maximal time to delay before sending an ACK.* Delayed ACK的最大延遲時間,一般為200ms*/ #define TCP_DELACK_MAX ((unsigned) (HZ/5))/* Delayed ACK的最小延遲時間,一般為40ms */ #define TCP_DELACK_MIN ((unsigned) (HZ/25))?
TCP_QUICKACK選項
?
TCP_QUICKACK用于讓本端立即發(fā)送ACK,而不進行延遲確認。
需要注意的是,這個選項并不是持久的,之后還是有可能進入延遲確認模式的。
所以如果需要一直進行快速確認,要在每次調用接收函數(shù)后都進行選項設置。
?
int?quickack = 1; /* 啟用快速確認,如果賦值為0表示使用延遲確認 */
setsockopt(fd, SOL_TCP, TCP_QUICKACK, &quickack, sizeof(quickack));
#define TCP_QUICKACK 12 /* Block / reenable quick acks */static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,unsigned int optlen) {...case TCP_QUICKACK:if (! val) {icsk->icsk_ack.pingpong = 1; /* 禁用快速確認模式 */} else {icsk->icsk_ack.pingpong = 0; /* 啟用快速確認模式 *//* 如果當前有ACK需要發(fā)送,就立即發(fā)送 */if (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) && inet_csk_ack_scheduled(sk)) {icsk->icsk_ack.pending |= ICSK_ACK_PUSHED; /* 允許在快速模式中立即發(fā)送 *//* 通常當接收隊列中有數(shù)據(jù)復制到用戶空間時,會調用此函數(shù)來判斷是否需要立即發(fā)送ACK。* 這里的用法比較特殊,由于設置了ICSK_ACK_PUSHED標志,且處于快速確認模式中,* 必然會立即發(fā)送ACK。*/tcp_cleanup_rbuf(sk, 1); /* 如果選項的值為偶數(shù),那么立即退出快速確認模式。* 原來選項值不限于0和1,還分奇偶的:)*/if (! (val & 1)) icsk->icsk_ack.pingpong = 1;}}break;... }?
當接收隊列中有數(shù)據(jù)復制到用戶空間時,會調用tcp_cleanup_rbuf()來判斷是否要立即發(fā)送ACK。
?
(1) 如果現(xiàn)在有ACK需要發(fā)送,滿足以下條件之一,就可以立即發(fā)送:
1. icsk->icsk_ack.blocked為1,之前有Delayed ACK被用戶進程阻塞了。
2. 接收緩沖區(qū)中有一個以上的全尺寸數(shù)據(jù)段仍然是NOT ACKed (所以經常是收到2個全尺寸段后發(fā)送ACK)
3. 本次復制到用戶空間的數(shù)據(jù)量大于0,且滿足以下條件之一:
??? 3.1?設置了ICSK_ACK_PUSHED2標志
??? 3.2 設置了ICSK_ACK_PUSHED標志,且處于快速確認模式中
?
(2) 如果原來沒有ACK需要發(fā)送,但是現(xiàn)在的接收窗口顯著增大了,也需要立即發(fā)送ACK通知對端。
這里的顯著增大是指:新的接收窗口大小不為0,且比原來接收窗口的剩余量增大了一倍。
/* Clean up the receive buffer for full frames taken by the user,* then send an ACK if necessary. COPIED is the number of bytes* tcp_recvmsg has given to the user so far, it speeds up the calculation* of whether or not we must ACK for the sake of a window update.*/void tcp_cleanup_rbuf (struct sock *sk, int copied) {struct tcp_sock *tp = tcp_sk(sk);bool time_to_ack = false;/* 獲取接收隊列的頭一個數(shù)據(jù)段 */struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);/* copied_seq: Head of yet unread data,應用程序下次從這里開始復制數(shù)據(jù)。* 這里檢查在發(fā)送隊列中,已復制到用戶空間的數(shù)據(jù)段是否被清理了。*/WARN(skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq), "cleanup rbuf bug: copied %X seq %X rcvnxt %X\n", tp->copied_seq,TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt);/* 如果現(xiàn)在有ACK需要發(fā)送,滿足以下條件之一則可以立即發(fā)送 */if (inet_csk_ack_scheduled(sk)) {const struct inet_connection_sock *icsk = inet_csk(sk);/* 1. Delayed ACKs frequently hit locked sockets during bulk receive.* 2. Once-per-two-segments ACK was not sent by tcp_input.c.* 3. copied >0 and ICSK_ACK_PUSHED2 set.* 4. copied > 0 and ICSK_ACK_PUSHED and in quickack mode.*/if (icsk->icsk_ack.blocked || tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss ||(copied > 0 && ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) || ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && ! icsk->icsk_ack.pingpong)) &&! atomic_read(&sk->sk_rmem_alloc)))time_to_ack = true;} /* We send an ACK if we can now advertise a non-zero window which has* been raised "significantly" - at least twice bigger.* Even if window raised up to infinity, do not send window open ACK in states,* where we will not receive more. It is useless.*/if (copied > 0 && ! time_to_ack && ! (sk->sk_shutdown & RCV_SHUTDOWN)) {__u32 rcv_window_now = tcp_receive_window(tp); /* 當前接收窗口的剩余量 *//* 如果當前接收窗口的剩余量小于最大值的一半 */if (2 * rcv_window_now <= tp->window_clamp) {/* 根據(jù)剩余的接收緩存,計算新的接收窗口的大小。* 因為這次復制,很可能空出不少接收緩存,所以新的接收窗口也會相應增大。*/__u32 new_window = __tcp_select_window(sk);/* Send ACK now, if this read freed lots of space in our buffer.* Certainly, new_window is new window.* We can advertise it now, if it is not less than current one.* "Lots" means "at least twice" here.*//* 如果新的接收窗口不為0,且比原來接收窗口的剩余量大了一倍以上,就說接收窗口顯著增大了。* 而當接收窗口顯著增大時,也需要立即發(fā)送ACK告知對端。*/if (new_window && new_window >= 2 * rcv_window_now)time_to_ack = true;}}if (time_to_ack)tcp_send_ack(sk); /* 發(fā)送ACK給對端 */ } /* 計算當前接收窗口的剩余量 */ /* Compute the actual receive window we are currently advertising.* Rcv_nxt can be after the window if our peer push more data than* the offered window.*/ static inline u32 tcp_receive_window (const struct tcp_sock *tp) {s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;if (win < 0)win = 0;return (u32) win; }?
轉載于:https://www.cnblogs.com/aiwz/p/6333256.html
總結
以上是生活随笔為你收集整理的TCP的ACK确认系列 — 快速确认的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据处理程序的一点经验
- 下一篇: 关于手思3.0 代码规范