第三章:運輸層 |《計算機網路:自頂向下方法》

一、概述和運輸層服務

運輸層為運行在不同主機上的應用程序之間提供邏輯通信。

應用報文通過運輸層被劃分為腳效果的塊,加上運輸層首部形成運輸層報文段,報文段通過網路層被封裝成網路層分組(數據報)向目的地發送。

1、運輸層和網路層的關係

運輸層為運行在不同主機上的應用程序之間提供邏輯通信。網路層提供主機之間的通信。

考慮有兩個家庭,一家位於美國東海岸,一家位於美國西海岸,每家有12個孩子。東海岸的孩子們是西海岸家庭孩子們的堂兄弟姐妹。這兩個家庭的孩子們喜歡彼此通信,每個人每周要給每個堂兄弟姐妹寫一封信,每封信都用單獨的信封通過傳統的郵政服務發送。因此,每個家庭每周向另一家庭發送144封信。(如果他們有電子郵件的話,這些孩子可以省不少錢!)每個家庭有一個孩子負責收發郵件,西海岸家庭是Ann而東海岸家庭是Bill。每周Ann去她所有的兄弟姐妹那裡收集郵件,並將這些郵件交到每天到家門口的郵政運輸車上。當信件到達西海岸家庭時,Ann也負責將信件發到她的兄弟姐妹手上,東海岸家庭中Bill也負責類似工作。

網路層就像郵遞員,而運輸層就像Ann和Bill。

2、網際網路運輸層概述

IP:運行在網路層上的不可靠服務。

UDP:非連接、運輸層上的不可靠服務。

TCP:面向連接,具有擁塞控制的可靠數據傳輸服務。

多路復用:上面例子中Ann和Bill將兄弟姐妹的信一起交給郵遞員。

多路分解:Ann和Bill將郵遞員送過來的信分給相應的兄弟姐妹。

二、多路復用和多路分解

參考連接:TCP的多路復用和分解

1、無連接的多路復用與多路分解(UDP)

一個UDP套接字是由一個二元組來全面標誌的,該二元組包含一個目的IP地址和一個目的埠號,因此如果兩個UDP報文段有不同的源IP地址和/或源埠號,但是具有相同的目的IP地址和目的埠號,那麼這兩個報文段將通過相同的套接字被定向到相同的進程。

UDP報文格式

  • 長度欄位:指示了在UDP報文段中的位元組數(首部加數據,以位元組為單位)
  • 檢驗和:接收方使用檢驗和來檢查在該報文段中是否出現差錯

UDP雖然實現了檢驗和,但是對恢復差錯無能為力,要麼它丟棄受損的報文段,要麼將受損的報文段交給應用程序並給出警告。

UDP可以是一對一、一對多、多對一、多對多的。

2、面向連接的多路復用和多路分解(TCP)

TCP套接字和UDP套接字的細微的差別是,TCP套接字是由一個四元組(源IP地址,源埠號,目的IP地址,目的埠號)來標識的。這樣當一個TCP報文段從網路到達另外一台主機時,該主機使用全部的4個值來將報文段定向(分解)到相應的套接字。特別與UDP不同的是,兩個具有不同的IP地址的或者是源埠號的到達TCP報文段將被定向到兩個不同的套接字,除非TCP報文段攜帶了初始創建連接的請求 。伺服器主機可以支持很多並行的TCP套接字,每一個套接字和一個進程相聯繫,並由其四元組來標識每一個套接字。當一個TCP報文段到達主機時,所有的四個欄位(源IP、源埠、目的IP、目的埠)被用來將報文段定向(分解)到相應的套接字。

TCP連接總是點對點的,所謂的「多播」,即在一次的發送操作當中從一個發送方將數據傳輸給多個接收方,對於TCP來說是不可能的。

假設主機A已經接收到了一個來自主機B的包含位元組0 ~ 355的報文段,以及另外的一個包含位元組900~1000的報文段,由於某種原因,主機A還沒有收到位元組536 ~ 899的報文段,因此主機A為了重構主機B的數據流,仍然在等待536和其後的位元組,於是A到B的下一個報文段將在確認號中包含536。因為TCP只確認該流中至第一個丟失位元組為止的位元組,所以TCP被稱為提供累計確認

三、無連接運輸:UDP

參考鏈接3.3 無連接運輸:UDP - 勿忘初心0924 - 博客園

UDP的優點:

  • 通過應用層可以精細地控制何時發送什麼數據
  • 無需連接建立
  • 無連接狀態
  • 分組首部開銷小

1、UDP報文段結構

  • 源埠號:發送方的埠號。
  • 目的埠號:接收方埠號。
  • 長度:包括首部在內的報文長度。
  • 檢驗和:用來差錯檢驗。只發現錯誤不糾正,錯了就扔。然後重發

2、檢驗和的計算:

就是源埠號(16位,兩個位元組),目的埠號(16位),長度(16位), 相加取反,相加的時候需要回卷,就是溢出的再加到後邊,例如:

1001 + 0111 = 10001,出現了5位,第一位的1,再加到後邊,等於,0001 + 1 = 0010.

得到檢驗和,這樣做的好處,在接收端收到數據的時候,將首部四個欄位加起來很明顯應該16個1,就說明沒錯,否則就說明有錯。

四、可靠數據傳輸原理

什麼是可靠數據傳輸?

發送的數據不會損壞、丟失、亂序地抵達目的地的傳輸。

1、構造可靠的傳輸協議

參考鏈接:

可靠數據傳輸原理 | YieldNull

TCP/IP之可靠數據傳輸原理

1)經完全可靠信道的可靠數據傳輸rdt1.0

假設信道完全可靠,從發送端發送的數據會按序,無損,不丟失地交付到接收端。

在發送端:

  1. rdt_send(data) 接受來自高層的數據
  2. make_pkt(data) 將數據分組
  3. udt_send(packet) 將分組發送到信道

在接受端:

  1. rdt_rcv(packet) 從信道接受數據分組
  2. extract(packet ,data) 從分組中取出數據
  3. deliver_data(data) 將數據上傳給較高層

在這種情況下,接受端不需要反饋任何信息給發送端,因為數據一定會按序無損到達。

2)經具有比特差錯信道的可靠數據傳輸rdt2.0

底層信道更為實際的模型是分組中的比特可能受損,我們假定即使數據受損,分組也會不丟失地到達。

在這種情況下,由於信道會造成數據差錯,為了使分組能無差錯、按序傳輸,則需要

  • 差錯檢測:接收端需要檢測出數據在哪裡出錯了,比如在分組首部中加入校驗和欄位
  • 接收方反饋:接收端要告訴發送端,某個分組的數據在傳輸過程中出錯了。數據沒錯就發送ACK(肯定確認),數據錯了就發送NAK(否定確認)
  • 重傳:發送端收到接受端的反饋後,要將出錯的分組重傳

我們假設,接收方反饋的信息,ACK 或 NAK 只需要 1 bit大小,而且在傳輸過程中不會損壞,丟失。也就是說,發送方一定會按序收到正確的反饋

在發送端:

  1. rdt_send(data) 接受來自高層的數據
  2. make_pkt(data,checksum) 將數據分組並將用於差錯檢測的校驗和信息寫入分組首部
  3. udt_send(sndpkt) 將分組發送到信道
  4. rdt_rcv(rcvpkt) 接收從接收方發來的反饋信息。若為肯定確認isACK(rcvpkt),則本次傳輸完成,繼續等待上層的調用;若為否定確認isNAK(rcvpkt),則說明數據出錯,因此要重傳數據,然後等待反饋結果,即重複本步驟。

在接收端

  1. rdt_rcv(rcvpkt) 接收分組,然後判斷數據是否損壞
  2. corrupt(rcvpkt)若數據損壞,則通過make_pkt(NAK)生成否定的反饋信息sndpkt
  3. uncorrupt(rcvpkt)若數據未損壞,則extract(packet ,data) 從分組中取出數據,然後deliver_data(data) 將數據上傳給較高層。並則通過make_pkt(ACK)生成肯定的反饋信息sndpkt
  4. 發送反饋信息udt_send(sndpkt)

3)經具有比特差錯信道的可靠數據傳輸rdt2.1(解決ACK或NAK受損)

這樣貌似可以解決數據受損問題了,然而,我們還沒有考慮到ACK 或者 NAK 數據受損的情況。問題的根源在於,如果一個ACK 或 NAK分組受損,發送方就無法知道接收方是否正確接收了上一次發送的分組。面對受損分組時,假若發送方重傳之前的分組,那麼當受損分組為ACK時,會造成分組冗餘;假若將其視為ACK,接著發送下一個分組,倘若受損分組為NAK,又會造成分組缺失。那麼遇到受損反饋分組時,發送方應當怎樣處理呢?

有這樣一個方法:發送方遇到受損反饋分組時,一律重傳;在接收端判斷該分組是否已經成功接收。那麼接收端該怎麼判斷呢?

我們假定信道不丟失分組,只會發生分組受損情況,分組能不丟失地到達接收端。為了保證分組無損壞、按序傳輸,我們在分組中添加一個新的欄位,讓發送方對其數據進行編號(0,1,0,1交替進行編號)。這樣,接收方就能通過分組的編號來判斷收到的分組是屬於上一個分組的重傳(編號與上一個分組的編號相同),還是下一個新的分組(編號與上一個分組的編號不同)。

發送方有限狀態機描述圖:

過程如下:

  1. 等待並接受上層調用,在數據分組首部添加編號0,(make_pkt(0,data,checksum))。並將分組發入信道
  2. 等待接收方的反饋信息,收到反饋信息後
  • 若信息受損或者是NAK,則按照原編號重傳分組,回到步驟2
  • 若是ACK,進入步驟3。
  1. 等待並接受上層調用,在數據分組首部添加編號1,(make_pkt(1,data,checksum))。並將分組發入信道.
  2. 等待接收方的反饋信息,收到反饋信息後
  • 若信息受損或者是NAK,則按照原編號重傳分組,回到步驟4
  • 若是ACK,進入步驟1。

接收方有限狀態機描述圖:

過程如下:

  1. 等待編號為0的分組到達。接受分組,抽取出數據以及編號
  • 若分組損壞,發送NAK,重複步驟1
  • 若編號為1,發送ACK,重複步驟1
  • 若編號為0,則將數據發給上層,發送ACK,進入步驟2
  1. 等待編號為1的分組到達。接受分組,抽取出數據以及編號
  • 若分組損壞,發送NAK,重複步驟2
  • 若編號為0,發送ACK,重複步驟2
  • 若編號為1,則將數據發給上層,發送ACK,進入步驟1

接收端接收到冗餘分組時,仍要發送ACK,以便於發送端發送下一個分組。但是接受到的冗餘數據會被捨棄,並不會再次傳給上層。

4)經具有比特差錯信道的可靠數據傳輸rdt2.2(無NAK)

NAK 只在接受端遇到受損分組時發送,其實我們可以捨棄NAK。利用分組的編號,在遇到受損分組時,發送ACK,並附帶上一次成功接受的分組對應的序號。發送端接受ACK後,將其序號與當前要發送的分組序號相比,就知道要重傳還是發送新的分組了。

發送方有限狀態機描述圖:

過程如下:

  1. 等待並接受上層調用,在數據分組首部添加編號0,並將分組發入信道。
  2. 等待接收方的反饋信息,收到反饋信息後,提取出對應的編號。
  • 若信息受損或者是ACK 1,則按照原編號重傳分組,回到步驟2
  • 若是ACK 0,進入步驟3。
  1. 等待並接受上層調用,在數據分組首部添加編號1,並將分組發入信道。
  2. 等待接收方的反饋信息,收到反饋信息後,提取出對應的編號
  • 若信息受損或者是ACK 0,則按照原編號重傳分組,回到步驟4
  • 若是ACK 1,進入步驟1。

接收方有限狀態機描述圖:

  1. 等待編號為0的分組到達。接受分組,抽取出數據以及編號
  • 若分組損壞,或若編號為1,發送ACK1,重複步驟1
  • 若編號為0,則將數據發給上層,發送ACK 0,進入步驟2
  1. 等待編號為1的分組到達。接受分組,抽取出數據以及編號
  • 若分組損壞,或若編號為0,發送ACK0,重複步驟2
  • 若編號為1,則將數據發給上層,發送ACK1,進入步驟1

5)經具有比特差錯的丟包信道的可靠數據傳輸rdt3.0

上述分析中,我們假設信道可以出現比特差錯,但是分組會不丟失地到達接收方。而在實際情況中,發送方發送的分組可能會丟失(比如被路由器丟棄),下面分析怎麼監測到分組的丟失,以及怎麼重傳丟失的分組。

當分組丟失時,發送方會一直等待接收方的反饋信息,而接收方也會一直等待新的分組到來,這樣以來,系統就沒辦法工作了。因此,我們可以在發送方設置一個定時器,估計一下發送方與接收方之間數據的往返時延,每當發送一個分組時,就啟動定時器,若超過預定的時間值,發送方則重傳分組。對於發送方,不知道是發送的數據丟失還是ACK丟失,那麼重傳就是萬能靈藥了;對於接收方,不管數據有沒有丟失,收到冗餘分組時丟棄即可。

發送方有限狀態機描述圖:(接收方與rdt 2.2 相同)

過程如下:

  1. 等待並接受上層調用,在數據分組首部添加編號0,並將分組發入信道,啟動定時器。
  2. 等待接收方的反饋信息。
  • 若超時,則按照原編號重傳分組,啟動定時器,回到步驟2。
  • 若未超時,收到反饋信息後,提取出對應的編號。
    • 若信息受損或者是ACK 1,則按照原編號重傳分組,回到步驟2
    • 若是ACK 0關閉定時器,進入步驟3。
  1. 等待並接受上層調用,等待過程中可能還會收到接收方發來的數據,忽略之。接受上層發來的數據後,在數據分組首部添加編號1,並將分組發入信道,啟動定時器。
  2. 等待接收方的反饋信息。
  • 若超時,則按照原編號重傳分組,啟動定時器,回到步驟4。
  • 若未超時,收到反饋信息後,提取出對應的編號。
    • 若信息受損或者是ACK 0,則按照原編號重傳分組,回到步驟4
    • 若是ACK 1關閉定時器,進入步驟5。
  1. 等待並接受上層調用,等待過程中可能還會收到接收方發來的數據,忽略之。在數據分組首部添加編號0,並將分組發入信道,啟動定時器,進入步驟2。

發送方只要在發送分組時就會啟動定時器,只有接受到正確的ACK時才會關閉定時器。

為什麼在等待上層調用時還會收到接收方的反饋信息呢?因為接收方在超時後發送了冗餘分組,這些分組並沒有丟失,而是延遲到達接收方。

基於rdt 3.0協議的數據傳輸運行示意圖:

2、流水線可靠數據傳輸協議

我們從rdt 1.0一步步演變到rdt 3.0, 已經實現了一個經具有比特差錯的丟包信道的可靠數據傳輸協議,此協議已經可以在現實生活中真實的信道上面運行了。然而由於該協議是停等協議,也就是必須成功接收到接收方的反饋信息,才能傳遞下一個分組(不論是傳遞新分組還是重傳)。這樣雖然能很好地保證分組能按序到達接收端,但是數據傳輸效率十分低下,每次傳輸都至少要等待一個往返時延(Round-Trip Time)。那麼怎麼才能加快傳輸速率呢?

可以使用流水線技術,也就是發送端可以同時發送多個分組到信道中,不需要依次等待上一個分組確認到達之後再發送下一個分組。當然,萬事有利也有弊,儘管改善了傳輸速率,但是也有一些弊端:

  • rdt 3.0比特交替分組編號方式不再合適,要增加編號範圍。
  • 分組可能會失序到達,雙方都需要建立緩衝區。由於會有多個分組同時在信道中傳輸,分組很可能會失序到達接收方,因此接收方要緩存失序到達的分組;分組也很可能會失序地要求重傳,故發送方也需要緩存已發送但未確認的分組。

那麼,在流水線情況下,怎麼處理數據的丟失、損壞、失序和超時到達呢?目前有退回N步(Go Back N)選擇重傳(Selective Repeat)兩種處理方式。

3、退回N步(GBN)

發送的分組會被依次編號,發送端會記錄哪些分組已經被成功接收。對於分組序號的管理如下:

最早未確認的分組編號為base,最小未使用序號為nextseqnum,將所有分組分為四個區段:

  • [0, base-1] 表示已經發送並被確認的分組
  • [base, nextseqnum-1] 表示已發送但是未確認
  • [nextseqnum, base+N-1] 表示能用於那些要立即被發送的分組
  • 大於base+N 的分組不能用,因為未被確認的分組數最多為N。

base對應的分組被確認之後,窗口才會向右移動。N被稱為窗口長度。限制窗口長度是要進行流量控制與擁塞控制。

序號怎麼分配確定之後,序號的範圍怎麼定呢?一般序號會承載在分組首部的一個固定的欄位中,序號範圍由欄位大小k進行確定,為[0,2k-1]。當序號用完之後,進行取模運算,從頭開始編號。

發送端的有限狀態機描述圖

初始情況下,base=1, nextseqnum=1。發送方要響應三個事件:

  1. 上層的調用。此時要檢查窗口是否已滿,也就是比較nextseqnumbase+N
  • 若已滿,則將數據返回給上層,表示窗口已滿,然後上層會等會兒再試。在實際過程中,發送方更可能會緩存這些數據,或者使用同步機制,只允許上層在窗口未滿時才調用rdt_send()
  • 若未滿,則產生一個分組,並發送到信道。然後遞增nextseqnum。若此時只有該分組未確認,則開啟定時器。
  1. 收到發送方反饋。若反饋受損,則什麼都不用做,繼續等待反饋。若收到一個序號為K的ACK,由於接收方採用累計確認,則表示K以及其之前的分組以及成功被接收。故把base置為K+1,若此時base=nextseqnum則表示所有分組都已經確認了,那麼就關掉定時器,等待上層調用發送數據。若還有分組未確認,則重啟定時器。整個過程中只使用了一個定時器,它被視為最早的已發送但未被確認的分組的定時器
  2. 超時事件。當超時時間發生時,GBN協議會重傳所有已發送但未被確認的分組。協議的名字退回N步就是來源於此

對於接收方,有限狀態機描述圖

接收方採用累計確認,數據要按序交付。它會丟棄到所有的失序分組,比如它期待接受序號為k的分組,而序號為k+1的分組卻先到達了,此時接收方會丟棄該分組(或者緩存),並發送ACK k。所以當它發送ACK N時,則表示N以前的分組都已經成功接收。累計確認由expectedseqnum進行控制。接收方不需要維護接收緩衝區,只需要丟棄到失序分組即可。此方法雖然簡單,但是會導致更多的分組重傳。

GBN傳輸示意圖

4、選擇重傳

GBN協議雖然使用流水線提高了傳輸速率,但是仍會引起一些性能問題。當窗口長度與信道時延都比較大時,一旦信道中的某個分組丟失或者超時,則會引起大量分組重傳。而接收方的累計確認機制,也會丟棄很多有用的分組而造成重傳。

選擇重傳是對退回N步協議的改進,即發送方只會重傳那些它懷疑在接收方出錯(丟失或受損)的分組從而避免不必要的重傳。同時,接收方也不會盲目地丟失失序的分組,而是將其緩存起來。

發送方的事件與動作:

  1. 從上層接收到數據。當從上層接收到數據之後,發送方判斷窗口是否已滿,若未滿就將數據打包發送;若已滿,則像GBN一樣,要麼將數據緩存,要麼將其返回給上層。
  2. 超時。SR中,每個分組都會有一個獨立的定時器,因為超時發送之後,只會重傳一個分組。
  3. 收到反饋信息。若反饋信息受損,則丟棄之。未受損,則從中提取出分組序號。若分組序號在發送窗口內,則將該分組標記為已確認。若該分組剛好是send_base,則將窗口移動到最小號的未確認分組處。如果窗口移動了,而且有待發送的分組(緩存的)落在窗口內,則發送之。

SR的接收方與GBN不同,當接收到未受損的失序分組時,會將其緩存起來。直到缺失的分組到來,與之形成一個連續的分組序列,才將整個序列按序交付給上層。接收方的事件與動作如下:

  1. 在[rcv_base, rcv_base+N-1]內的分組被正確接收,此時會返回對應序號的ACK給發送方。若該分組未之前未接收,則緩存之;若已接收,則丟棄之。若該分組恰好為rcv_base,則按序依次交付已確認的分組給上層,並同時移動rcv_base,在未確認的分組處停下。
  2. 在[rcv_base-N , rcv_base-1]內的分組被正確接收,必須返回一個對應序號的ACK
  3. 其它情況,直接丟棄之。

為啥在第二部還要發送已經確認過的分組對應過的ACK呢?還有,為什麼是當前窗口左側N個分組需要確認,而更久以前的不需要呢?考慮這種情況:

大量的分組成功地從發送方無損到達接收方,接收方也發送了對應的ACK。然而由於從接收方到發送方的信道丟包嚴重,導致大量的ACK都沒有成功到達發送方。超時之後,發送方只好重傳分組。極端情況下,接受方已經確認了所有N個分組,然而接受方卻連一個無損的ACK都沒有收到。此時,rcv_base-send_base=N,之後,發送方重傳的分組到達了,要是不再次發送對應的ACK,那麼發送方就會永遠重傳下去了。故發送方需要從當前窗口左側N個分組處開始重複確認成功接受的分組。

結合上圖以及上述示例,可以發現,發送方與接收方的窗口並不是同步的。因為ACK也會在信道中出錯。

五、面向連接的運輸TCP

1、TCP連接

參考連接:(二)TCP 報文結構 · 理解 TCP 和 UDP

TCP連接是面向連接的,因為一個應用進程可以開始向另一個應用進程發送數據之前,這兩個進程必須先相互「握手」。(相互發送某些預備報文段,以建立確保數據傳輸的參數)

全雙工服務 : A和B之間如果存在一條TCP連接,則應用層數據從A流向B的同時,就有數據從B流向A

點對點: 單個發送只能有一個接收方

MSS:最大報文長度(Maximum Segment Size)

2、TCP報文段結構

TCP報文段結構

  • 32位序號: 32位的序列號由接收端計算機使用,重組分段的報文成最初的形式,當SYN出現,序列碼實際上是初始序列碼(Initial Sequence Number,ISN),而第一個數據位元組是ISN+1。這個序列號(序列碼)可以用來補償傳輸中的不一致。
  • 32位確認序號:32位的序列號由接收端計算機使用,重組分段的報文成最初的形式,如果設置了ACK控制位,這個值表示一個準備接收的包的序列碼。
  • 4位首部長度:指示TCP頭部大小(以32bit為單位),指示何處數據開始,由於TCP選項的原因,TCP首部長度是可變的。(但是通常選項為空,TCP頭部典型長度為20位元組,所以首部長度通常為5,即1001).
  • 16位窗口大小:用來表示想要收到的每個TCP數據段的大小。TCP的流量控制由連接的每一端通過聲明窗口的大小來提供。窗口的大小為位元組數,起始於確認序號欄位指明的值,這個值是接收端正期望接收到的位元組。窗口的大小是一個16位元組欄位,因而窗口大小最大為65535位元組。
  • 16位檢驗和:16位TCP頭部檢驗和。源主機基於數據內容計算一個數值,目的主機要和源主機計算的結果一致,從而驗證數據的有效性。檢驗和覆蓋的是整個的TCP報文段:這是一個強制性的欄位,一定是由發送端計算和存儲,並由接收端進行驗證。
  • URG:緊急標誌,為1時表示有效,緊急數據的最後一個位元組由16bit的緊急數據指針欄位指出。當緊急數據存在時,
  • ACK:確認標誌。表明確認編號欄有效,大多數情況下該標識位是置位的。TCP報頭內的確認編號欄內包含的確認編號(W+1)為下一個預期接收到的序列編號,同時提示遠端系統已經成功的接收到了所有數據。
  • PSH:推標誌。該標誌置位時,接收端不將該數據進行隊列處理,而是儘可能快地將數據轉由應用處理(接收方立即將數據交給上層)。在處理Telnet或rlogin等交互模式的連接時,該標誌總是置位的。
  • RST:複位標誌。用於複位(重置)相應的TCP連接。
  • SYN:同步標誌。表明同步序列編號欄有效。該標誌僅在三次握手建立TCP連接時有效。它提示TCP連接的服務端檢查序列編號,該序列編號為TCP連接初始端(一般是客戶端)的初始序列編號。
  • FIN:結束標誌。

TCP四次揮手斷開連接

四次揮手(Four-Way Wavehand)是指斷開一個TCP連接時需要客戶端和伺服器總共發送四個包以確認連接的斷開。在socket()編程中,這一個過程由客戶端或者伺服器端的任意一方執行close來觸發。整個流程圖如下:

TCP四次揮手

由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務後,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉,上圖描述的即是如此。

  • 第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
  • 第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
  • 第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
  • 第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接著發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。

假設客戶端應用程序決定要關閉TCP連接,這引起客戶TCP發送一個帶有FIN比特被置為1的TCP報文段,並進入FIN_WAIT_1狀態。當處於這個狀態時,客戶TCP等待一個來自伺服器的帶有確認的TCP報文段,當它收到該報文段時,客戶TCP進入FIN_WAIT_2狀態。當處於這一狀態時,客戶等待來自伺服器的FIN比特被置為1的另外一個報文段;當收到該報文段後,客戶TCP對伺服器的報文段進行確認,並進入TIME_WAIT狀態。假定ACK丟失,TIME_WAIT狀態使TCP客戶重傳最後的確認報文。經過等待以後,連接就正式關閉,客戶端的所有資源將會被釋放。

為什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?

原因有二:

a、保證TCP協議的全雙工連接能夠可靠關閉

b、保證這次連接的重複數據段從網路中消失

先說第一點,如果Client直接CLOSED了,那麼由於IP協議的不可靠性或者是其它網路原因,導致Server沒有收到Client最後回復的ACK。那麼Server就會在超時之後繼續發送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連接,最後Server就會收到RST而不是ACK,Server就會以為是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP協議不符合可靠連接的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,當再次收到FIN的時候,能夠保證對方收到ACK,最後正確的關閉連接。

再說第二點,如果Client直接CLOSED,然後又再向Server發起一個新連接,我們不能保證這個新連接與剛關閉的連接的埠號是不同的。也就是說有可能新連接和老連接的埠號是相同的。一般來說不會發生什麼問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接埠號是一樣的,如果前一次連接的某些數據仍然滯留在網路中,這些延遲數據在建立新連接之後才到達Server,由於新連接和老連接的埠號是一樣的,又因為TCP協議判斷不同連接的依據是socket pair,於是,TCP協議就認為那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP連接還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連接的所有數據都從網路中消失。

3、往返估計時間和超時

參考鏈接:TCP中往返時間的估計與超時 - SakuraOne - 博客園

1)估計往返時間

報文段的樣本RTT(表示為SampleRTT)為某報文段發出到對該報文段的確認被收到之間的時間量大多數TCP的實現僅在某個時刻做一次SampleRTT測量,而不是為每個報文段測量一個SampleRTT。

也就是說,在任何時刻,僅為一個已發送但是目前尚未被確認的報文段估計SampleRTT,從而產生一個接近每個RTT的新SampleRTT值。 另外,TCP絕不為被重傳的報文計算SampleRTT;它僅為傳輸一次的報文段測量SampleRTT。

由於路由器的擁塞和端系統負載的變化,這些報文段的SampleRTT是波動的,所以給定的任何SampleRTT都是非典型的。因此要取得典型的RTT,就要對SampleRTT進行加權取值。

EstimatedRTT = (1-α)*EstimatedRTT + α*SampleRTT

[RFC 6298]中給出α的參考值為0.125

對於最近的樣本賦予較大的權值,是因為越近的樣本越能反應網路當前的擁塞狀況。

從統計學觀點講,這種平均被稱為指數加權移動平均(EWMA)

除估算RTT外,測量RTT變化也是很有用的!

[RFC 6298]定義了RTT偏差 DevRTT,用於估算SampleRTT偏離EstimatedRTT的程度:

DevRTT = (1-β)*DevRTT + β*|SampleRTT - EstimatedRTT|

DevRTT是SampleRTT和EstimatedRTT之差的指數加權移動平均

如果SampleRTT的波動小那麼DevRTT的波動便會小,反之亦此。

第一次的DevRTT=1/2(SampleRTT),以後按公式來計算,推薦β為0.25

2)設置和管理重傳超時間隔

超時時間間隔應該大於EstimatedRTT並且不能大於太多。超時時間間隔為EstimatedRTT加上一些餘量。並且在SampleRTT值波動大時,餘量較大;當波動較小時,餘量較小。因此就用到了DevRTT。由此得出TCP重傳時間間隔計算公式:

TimeoutInterval = EstimatedRTT + 4*DevRTT

推薦的初始TimeoutInterval為1秒。 出現超時後,TimeoutInterval直接加倍。

因為此次重傳可能是報文確認ACK因為網路擁塞而延遲到達從而導致報文重傳,重傳報文後,不久,ACK到達,會導致SampleRTT變小,進而使TimeoutInterval變小,使後面的報文出現過早超時!

一旦報文段收到並更新EstimatedRTT後,TimeoutInterval又使用上述公式。

4、可靠數據傳輸

超時重傳、快速重傳。

5、流量控制

六、擁塞控制原理

七、TCP擁塞控制、

參考鏈接:TCP的流量控制和擁塞控制 - CSDN博客

幾種擁塞控制方法

慢開始( slow-start )、擁塞避免( congestion avoidance )、快重傳( fast retransmit )和快恢復( fast recovery )。

1、慢開始

發送方維持一個擁塞窗口 cwnd ( congestion window )的狀態變數。擁塞窗口的大小取決於網路的擁塞程度,並且動態地在變化。發送方讓自己的發送窗口等於擁塞。

發送方控制擁塞窗口的原則是:只要網路沒有出現擁塞,擁塞窗口就再增大一些,以便把更多的分組發送出去。但只要網路出現擁塞,擁塞窗口就減小一些,以減少注入到網路中的分組數。

慢開始演算法:當主機開始發送數據時,如果立即所大量數據位元組注入到網路,那麼就有可能引起網路擁塞,因為現在並不清楚網路的負荷情況。因此,較好的方法是先探測一下,即由小到大逐漸增大發送窗口,也就是說,由小到大逐漸增大擁塞窗口數值。通常在剛剛開始發送報文段時,先把擁塞窗口 cwnd 設置為一個最大報文段MSS的數值。而在每收到一個對新的報文段的確認後,把擁塞窗口增加至多一個MSS的數值。用這樣的方法逐步增大發送方的擁塞窗口 cwnd ,可以使分組注入到網路的速率更加合理。

每經過一個傳輸輪次,擁塞窗口 cwnd 就加倍。一個傳輸輪次所經歷的時間其實就是往返時間RTT。不過「傳輸輪次」更加強調:把擁塞窗口cwnd所允許發送的報文段都連續發送出去,並收到了對已發送的最後一個位元組的確認。

另,慢開始的「慢」並不是指cwnd的增長速率慢,而是指在TCP開始發送報文段時先設置cwnd=1,使得發送方在開始時只發送一個報文段(目的是試探一下網路的擁塞情況),然後再逐漸增大cwnd。

2、擁塞避免

為了防止擁塞窗口cwnd增長過大引起網路擁塞,還需要設置一個慢開始門限ssthresh狀態變數(如何設置ssthresh)。慢開始門限ssthresh的用法如下:

  • 當 cwnd < ssthresh 時,使用上述的慢開始演算法。
  • 當 cwnd > ssthresh 時,停止使用慢開始演算法而改用擁塞避免演算法。
  • 當 cwnd = ssthresh 時,既可使用慢開始演算法,也可使用擁塞控制避免演算法。

擁塞避免演算法:讓擁塞窗口cwnd緩慢地增大,即每經過一個往返時間RTT就把發送方的擁塞窗口cwnd加1,而不是加倍。這樣擁塞窗口cwnd按線性規律緩慢增長,比慢開始演算法的擁塞窗口增長速率緩慢得多。

無論在慢開始階段還是在擁塞避免階段,只要發送方判斷網路出現擁塞(其根據就是沒有收到確認),就要把慢開始門限ssthresh設置為出現擁塞時的發送方窗口值的一半(但不能小於2)。然後把擁塞窗口cwnd重新設置為1,執行慢開始演算法。這樣做的目的就是要迅速減少主機發送到網路中的分組數,使得發生擁塞的路由器有足夠時間把隊列中積壓的分組處理完畢。

如下圖,用具體數值說明了上述擁塞控制的過程。現在發送窗口的大小和擁塞窗口一樣大。

  • <1>. 當TCP連接進行初始化時,把擁塞窗口cwnd置為1。前面已說過,為了便於理解,圖中的窗口單位不使用位元組而使用報文段的個數。慢開始門限的初始值設置為16個報文段,即 cwnd = 16 。
  • <2>. 在執行慢開始演算法時,擁塞窗口 cwnd 的初始值為1。以後發送方每收到一個對新報文段的確認ACK,就把擁塞窗口值另1,然後開始下一輪的傳輸(圖中橫坐標為傳輸輪次)。因此擁塞窗口cwnd隨著傳輸輪次按指數規律增長。當擁塞窗口cwnd增長到慢開始門限值ssthresh時(即當cwnd=16時),就改為執行擁塞控制演算法,擁塞窗口按線性規律增長。
  • <3>. 假定擁塞窗口的數值增長到24時,網路出現超時(這很可能就是網路發生擁塞了)。更新後的ssthresh值變為12(即變為出現超時時的擁塞窗口數值24的一半),擁塞窗口再重新設置為1,並執行慢開始演算法。當cwnd=ssthresh=12時改為執行擁塞避免演算法,擁塞窗口按線性規律增長,每經過一個往返時間增加一個MSS的大小。

強調:「擁塞避免」並非指完全能夠避免了擁塞。利用以上的措施要完全避免網路擁塞還是不可能的。「擁塞避免」是說在擁塞避免階段將擁塞窗口控制為按線性規律增長,使網路比較不容易出現擁塞。

3、快重傳和快恢復

如果發送方設置的超時計時器時限已到但還沒有收到確認,那麼很可能是網路出現了擁塞,致使報文段在網路中的某處被丟棄。這時,TCP馬上把擁塞窗口 cwnd 減小到1,並執行慢開始演算法,同時把慢開始門限值ssthresh減半。這是不使用快重傳的情況。

快重傳演算法首先要求接收方每收到一個失序的報文段後就立即發出重複確認(為的是使發送方及早知道有報文段沒有到達對方)而不要等到自己發送數據時才進行捎帶確認。

接收方收到了M1和M2後都分別發出了確認。現在假定接收方沒有收到M3但接著收到了M4。顯然,接收方不能確認M4,因為M4是收到的失序報文段。根據可靠傳輸原理,接收方可以什麼都不做,也可以在適當時機發送一次對M2的確認。但按照快重傳演算法的規定,接收方應及時發送對M2的重複確認,這樣做可以讓發送方及早知道報文段M3沒有到達接收方。發送方接著發送了M5和M6。接收方收到這兩個報文後,也還要再次發出對M2的重複確認。這樣,發送方共收到了接收方的四個對M2的確認,其中後三個都是重複確認。快重傳演算法還規定,發送方只要一連收到三個重複確認就應當立即重傳對方尚未收到的報文段M3,而不必繼續等待M3設置的重傳計時器到期。由於發送方儘早重傳未被確認的報文段,因此採用快重傳後可以使整個網路吞吐量提高約20%。

與快重傳配合使用的還有快恢復演算法,其過程有以下兩個要點:

  • <1>. 當發送方連續收到三個重複確認,就執行「乘法減小」演算法,把慢開始門限ssthresh減半。這是為了預防網路發生擁塞。請注意:接下去不執行慢開始演算法。
  • <2>. 由於發送方現在認為網路很可能沒有發生擁塞,因此與慢開始不同之處是現在不執行慢開始演算法(即擁塞窗口cwnd現在不設置為1),而是把cwnd值設置為慢開始門限ssthresh減半後的數值,然後開始執行擁塞避免演算法(「加法增大」),使擁塞窗口緩慢地線性增大。

下圖給出了快重傳和快恢復的示意圖,並標明了「TCP Reno版本」。

區別:新的 TCP Reno 版本在快重傳之後採用快恢復演算法而不是採用慢開始演算法。

也有的快重傳實現是把開始時的擁塞窗口cwnd值再增大一點,即等於 ssthresh + 3 X MSS 。這樣做的理由是:既然發送方收到三個重複的確認,就表明有三個分組已經離開了網路。這三個分組不再消耗網路 的資源而是停留在接收方的緩存中。可見現在網路中並不是堆積了分組而是減少了三個分組。因此可以適當把擁塞窗口擴大了些。

在採用快恢復演算法時,慢開始演算法只是在TCP連接建立時和網路出現超時時才使用。

採用這樣的擁塞控制方法使得TCP的性能有明顯的改進。

接收方根據自己的接收能力設定了接收窗口rwnd,並把這個窗口值寫入TCP首部中的窗口欄位,傳送給發送方。因此,接收窗口又稱為通知窗口。因此,從接收方對發送方的流量控制的角度考慮,發送方的發送窗口一定不能超過對方給出的接收窗口rwnd 。

發送方窗口的上限值 = Min [ rwnd, cwnd ]

當rwnd < cwnd 時,是接收方的接收能力限制發送方窗口的最大值。

當cwnd < rwnd 時,則是網路的擁塞限制發送方窗口的最大值。

PS:

廣告時間啦~

理工狗不想被人文素養拖後腿?不妨關注微信公眾號:

歡迎關注~


推薦閱讀:

滿分文書大全|如何寫一份招生官都拒絕不了的CS文書
大學使命
IntelliJ Idea 常用快捷鍵列表
關於引用的一點點想法
刷頂會必備 ?』?』 ? 2018年人工智慧頂會月曆

TAG:計算機網路 | 計算機網路庫羅斯,羅斯著書籍 | 計算機專業 |