天天看點

linux關于tcp協定ack的實作--發送端對ack的處理

前面的文章分析了接收端如何發送ack給發送端,總結一下就是立即ack,捎帶ack和延遲ack,現在看一下tcp的發送端是如何處理ack的,本質上tcp所謂的有連接配接就是雙方對于seq和ack的處理,對于seq,發送方是主動的,而接收端是被動的,但是對于ack則相反,是以參照tcp的流控以及擁塞控制加之性能因素的需要,首先要設計接收端如何發送ack,其次再來設計發送端如何處理,linux采納了rfc的建議(好像沒有不采納的OS,除非它猛到自己定義标準)。對于發送ack,前面已經說過了,對于如何處理ack,完全就是應付一下,要簡單的多,簡單的說,接收端隻會發送按序封包的最後一個未被确認的封包的seq作為其ack,但是對于發送端收到這個ack後如何處理,雖然比發送ack的邏輯和政策要簡單,但是也要徹底了解協定才能看懂代碼:

static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)

{

...

    if (after(ack, tp->snd_nxt))

        goto uninteresting_ack;

    if (before(ack, prior_snd_una))

        goto old_ack;

    if (!(flag&FLAG_SLOWPATH) && after(ack, prior_snd_una)) { //非備援的ack,正常,不進入快速重傳

        tcp_update_wl(tp, ack, ack_seq);

        tp->snd_una = ack;

        tcp_westwood_fast_bw(sk, skb);

        flag |= FLAG_WIN_UPDATE;  

    } else {

        if (ack_seq != TCP_SKB_CB(skb)->end_seq) //說明是捎帶ack

            flag |= FLAG_DATA;

        flag |= tcp_ack_update_window(sk, tp, skb, ack, ack_seq); //flag可能置update位

        if (TCP_SKB_CB(skb)->sacked) //如果是sack,則說明可能丢包了,有可能置上sack位,選擇确認接收端已經收到的封包,sack完全是為了提高性能的,實際上在分析代碼的時候可以忽略這種情況,隻有确認丢失封包的時候才會選擇确認(sack)

            flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);

        if (TCP_ECN_rcv_ecn_echo(tp, skb->h.th)) //路由器通知丢包,置上ece位

            flag |= FLAG_ECE;

        ...

    }

    ...

    flag |= tcp_clean_rtx_queue(sk, &seq_rtt); //盡量清除所有已經被确認的封包,如果有被确認的封包,則置上acked位

    if (tcp_ack_is_dubious(tp, flag)) { //如果沒有設定acked位,也沒有設定data位也沒有設定update位,或者存在sack或者ece位,則說明可能已經丢失封包,這裡判斷比較複雜,注意||運算符,隻要第一個比較對象傳回真就傳回,依次類推,flag如果有data位或者acked位,我們也不能确定就一定沒有丢失封包,因為ack由對端發送,可能的方式有好幾種,不僅僅是裸ack,還可能是捎帶ack,對于設定了acked位的flag也不能說就一定沒有丢失封包,因為雖然該ack确認一部分封包,但是後面的封包可能丢失,複雜的情況下還可能出現選擇确認-sack,是以還需要進一步判斷ece和sack标志,但是反過來可以說的是,如果既沒有data位,也沒有acked位,也沒有update位,則一定要進入重傳。

        ...//進入擁塞控制,快速重傳

        tcp_fastretrans_alert(sk, prior_snd_una, prior_packets, flag);

    } 

    ...//否則正常傳回

    return 1;

}

其中最麻煩的就是sack的相關邏輯了,前面說了,sack完全是為性能考慮的一個可選的機制,它可以使得發送端隻重傳丢失的封包,而不必重傳已經發來的有選擇的亂序的确認封包,這是通過tcp頭的選項進行配置的。使用sack機制前要允許sack選項,在備援ack發來的時候,它攜帶一些資訊,這些資訊包含哪些亂序的封包已經安全正确接收且被暫存在接收端了,如此一來發送端重傳封包的時候就不必再傳輸這些已經确認的亂序封包了,具體可以看一下tcp_sacktag_write_queue的代碼。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271788

繼續閱讀