一篇文章搞懂TCP協議

TCP概述

  • 面向連接的運輸層協議:應用程序在使用TCP協議之前,必須先建立TCP連接,數據傳輸完畢後,必須釋放已建立的TCP連接
  • 點對點:TCP連接只能有兩個端點,一對一
  • 可靠:通過TCP連接傳輸的數據,無差錯、不丟失、不重複、按需到達
  • 全雙工通信:連接的兩端都設有發送緩存和接收緩存,用來臨時存放雙向通信的數據
  • 面向字節流:數據通過自己序列發送,發送和接收的字節流順序必須完全一致

TCP報文段的首部格式

  • 源端口和目的端口:各佔2個字節
  • 序號:佔4字節,範圍是0~2的32次方減1,TCP在傳輸過字節流的每一個字節都按順序編號,編號循環使用
  • 確認號:佔4字節,期望收到對方下一個報文的數據字節的序號
  • 數據偏移:佔4位,標記報文段的數據起始處距離報文段的起始處的距離
  • 保留:佔6位
  • 緊急URG:當URG爲1時,表明緊急指針字段有效,它告訴接收方此報文段有緊急數據,必須優先接收,此時該報文不再需需要按照原定的順序排隊,直接插入到本報文數據的最前面,優先處理(比如Control + C命令)
  • 確認ACK:TCP規定,在連接建立後,所有傳送的報文段都必須把ACK置爲1
  • 推送PUSH:當輸出某些指令後立即需要收到對方響應,在這種情況下,TCP就可以使用push操作,發送方把PUSH置爲1,並立即創建一個報文發送出去,接收方收到PUSH爲1的報文段時,就立即將緩衝區的報文交付給應用進程,而不再等到整個緩衝區都填滿後再向上交付
  • 復位RST:當RST爲1時,表明TCP連接中出現嚴重差錯,必須釋放連接重新建立連接,可用於拒絕非法報文或非法連接
  • 同步SYN :在建立連接時用來同步序列號,當SYN=1而ACK=0時,表明建立連接請求,若對方同意建立連接,則應該在響應的報文段中使用SYN=1和ACK=1
  • 終止FIN:用來釋放連接,當FIN=1時,表示此報文發送方的數據已經發送完畢,請求釋放TCP連接
  • 窗口:佔2個字節,窗口值作爲接收方讓發送方設置其發送窗口的依據,可靠傳輸和流量控制都依賴窗口實現(後面詳細說)
  • 檢驗和:計算檢驗和時要在TCP報文段前面加上偉首部,用來檢驗傳輸數據是否有錯
  • 緊急指針:佔2個字節,在URG=1時纔有意義,標記緊急報文中的緊急數據的字節數,緊急數據結束後就是普通數據
  • 選項:規定每個報文段中的數據字段的最大長度MSS,一般在建立連接時,雙方將自己能夠支持的MSS寫入選項中,以後就按照這個數據傳輸數據。
  • 數據

報文段首部格式一定要記住,至少要大概理解後,再帶着疑問去看下邊,否則看下邊有可能會一臉懵逼。

TCP可靠傳輸的實現

                                                     

       TCP的可靠傳輸通過滑動窗口實現,滑動窗口以字節爲單位。凡是已發送過的數據,在未收到確認之前都必須暫時保留,以便在超時重傳時使用,窗口越大,發送方就可以在收到對方確認之前連續發送更多的數據,獲得更高的傳輸速率。滑動窗口可以分爲四部分:

  • 已發送並搜到確認
  • 已發送未收到確認
  • 允許發送但尚未發送
  • 不允許發送

       窗口依賴三個指針,P1,P2和P3,小於1的是已經發送並收到確認的字節,1和2之間的是已發送但未收到確認的字節,2和3之間是允許發送但尚未發送但字節,大於3爲不允許發送的字節,1和3之間的字節數即爲接收方規定的滑動窗口大小(流量控制)。接收方B只能對按需收到的最高序號給出確認,當指針2和指針3重合時,發送方必須停止發送數據,等待接收方的確認,爲了保證可靠傳輸,A必須等到B的確認才能繼續發送數據,爲了保證發送方不會一直等待,到達依賴超時計時器(動態計算超時重傳時間)規定的時間後,就重傳這部分數據,直到收到接收方B的確認。

超時重傳時間的選擇採用一個自適應算法,它會記錄一個報文段發出的時間,以及收到響應的確認的時間,作爲往返時間RTT,計算爲加權平均往返時間RTTs,RTTd爲RTT的偏差的加權平均值,通過這幾個值計算得到超時重傳時間RTO。計算的規則是:報文段每重傳一次,就把超時重傳時間RTO增大一些,當不再發生報文段的重傳時,根據下面公式計算RTO。

                                                                            RTO = RTTs + 4 * RTTd

TCP的流量控制

        流量控制就是讓發送方的發送速率不要太快,要讓接收方來得及接收,利用滑動窗口實現。發送方的發送窗口不能超過接收方給出的接收窗口大小。當接收方接收能力下降時,會縮小自己的接收窗口,發送方通過報文頭部的串口字段告知發送方。

       還以上面發送方爲A,接收方爲B作爲例子,當B的接收緩存滿之後,將自己的接收窗口置爲0,通過報文的窗口字段告知發送方A,過了一段時間後,B的應用程序將接收緩衝區的數據處理了一部分,接收窗口又有了空間,假設爲300,此時B向A發送報文告知對方接收窗口爲300,如果該報文在傳輸中丟失了,A不會一直傻傻地等待,在A收到B的0窗口報文後,就會開啓該連接設定的持續計時器,當超過計時器規定的時間後,A會向B發送一個0窗口探測報文段,B在收到這個探測報文後,會重新發送300的窗口報文段。此時有人會問,B的接收窗口大小爲0了,怎麼還能接收0窗口探測報文段?這裏需要記住:TCP規定,即使接收窗口爲0,也必須可以接收0窗口探測報文段、ACK=1的報文段和URG=1的報文段

TCP的擁塞控制

                                                   

TCP進行擁塞控制的算法有4個:慢開始、擁塞避免、快重傳、快恢復。

  1. 慢開始:將擁塞窗口大小設置爲1個發送方的最大報文段MSS(上面說過哈,報文首部的選項字段),每經過一個傳輸輪次,擁塞窗口就加倍,直到達到慢開始門限後開啓擁塞避免。
  2. 擁塞避免:當達到慢開始門限後,每收到一次接收方的確認ACK,就將擁塞窗口大小+1,直到網絡出現超時後,將慢開始門限調整爲原來的一半,執行慢開始算法。
  3. 快重傳:快重傳算法可以讓發送方儘早知道發生了個別報文段的丟失。由於接收方必須對發送方每個報文回覆確認ACK,假設發送方以此發送M1、M2、M3、M4、M5、M6報文,接收方收到1和2後都及時給出了確認,由於字節都是連續都,假設接收方沒有收到3,但收到了4,此時接收方會在收到4、5、6報文後都發送對2的確認,當發送方連續收到3個重複確認時,就知道接收方沒有收到報文段3,此時會立即重傳報文3。
  4. 快恢復:當發生快重傳時,發送方知道現在只是丟失了個別字段,所以不啓動慢開始,而是執行快恢復算法,將擁塞窗口調整爲慢開始門限的一半,執行擁塞避免算法。

下面通過一道題來理解:

問題:設tcp的擁塞窗口的慢開始門限值初始爲8,當擁塞窗口上升到12時網絡發生超時,那麼第13次傳輸時的擁塞窗口大小爲多少?

解答:

  1. 慢開始:0->1->2->4->8(傳輸4次)
  2. 到達慢開始門限8,進入擁塞避免: 8->9->10->11->12(傳輸4次)
  3. 增長到12發生超時,慢開始門限調整到6,慢啓動:0->1->2->4->6(傳輸4次)
  4. 6已經達到慢開始門限,進入擁塞避免:6->7(第十三次傳輸)
  5. 所以最終答案爲7

TCP連接的三次握手

                                                              

  1. 最開始的 Client 和 Server 都是處於 Closed,由於服務器端不知道要跟誰建立連接,所以其只能被動打開,然後監聽端口,此時 Server 處於 Listen 狀態;

  2. 而 Client 會主動打開,然後構建好 TCB 「SYN= 1,seq = x」,發送給服務器端,此時 Client 會將狀態置爲 SYN_SEND 「同步已發送」;

  3. 服務器端收到客戶端發來的同步請求後,會將狀態置爲 SYN_RECV「同步已接收」,同時會構建好 TCB「SYN = 1,seq = y,ACK = 1,ack = x + 1」發送給客戶端;

  4. 客戶端接收到了服務器端發來的傳輸控制塊之後,會將自己的狀態改爲 ESTABLISHED「建立連接」,然後發送確認報文(ACK= 1,seq = x + 1,ack = y + 1);

  5. 服務器端在收到了客戶端發來的報文之後,也將狀態置爲 ESTABLISHED「建立連接」,至此,三次握手結束,當然在這裏,可以帶 tcp 報文段信息過來了,因爲此時客戶端已經可以保證是可靠的傳輸了,所以在這一端可以發送報文段了。

題目1:爲什麼A最後還要再發送一次確認?

解答:爲了防止因爲網絡原因遲來的A連接請求到達B,B單方面建立連接浪費B的資源。假如A發送了連接請求,但未收到確認,於是A又重傳了一次連接請求,第二個連接請求正常連接並傳輸數據。此時A的第一個連接沒有丟失,只是延遲了,又到達了B,B誤以爲是A又發送的一次連接,假如此時B向A發出確認後,A會丟棄B的確認,假如沒有A最後一次的確認,B就會在發送確認後建立連接,白白浪費B的資源。

題目2:爲何不直接在第一次握手就帶上報文段消息,非要第三次纔可以帶?

解答:因爲 TCP 是要保證數據的不丟失且可靠,如果在第一次就帶上報文段消息,此次建立連接很有可能就會失敗,那麼就不能保證數據的不丟失了,在不可靠的機制上進行這種操作,換來的代價太大,每次發送報文段的資源也會增大,得不償失;而第三次握手的時候,客戶端已經知道服務器端準備好了,所以只要告訴服務器端自己準備好了就ok了,所以此時帶上報文段信息沒有任何問題。

TCP斷開的四次揮手

                                                    

  1. 最開始客戶端和服務器端都是 ESTABLISHED 的狀態,然後客戶端會主動關閉,而服務器端則是被動關閉。(主動關閉的我們認爲是客戶端)

  2. 客戶端發送一個 FIN 報文段,seq = 結束的報文段序號 + 1「假設爲 u」,告訴服務器端,客戶端需要關閉了,此時將客戶端的狀態變爲 FIN-WAIT-1,等待服務器端的反饋;

  3. 服務器在接收到了客戶端發來的 FIN 包之後,會發一條 ack報文反饋給客戶端,其中報文中包括 ACK = 1,seq = v,ack = u+1,告訴客戶端收到了客戶端要關閉的消息了,同時服務器端會通知應用進程需要關閉連接了,並將自己的狀態置爲 CLOSE-WAIT;

  4. 由於服務器端可能還有一些數據沒處理完,所以需要一段時間的等待,當處理完了之後,會再發一條報文,其中 FIN = 1,ACK = 1,seq = w,ack = u+1,告知客戶端,服務器端現在可以關閉了,並將服務器端的狀態由 CLOSE-WAIT 變爲 LAST-ACK;

  5. 客戶端在收到了服務器端發來的消息之後,會發一條ack報文「ACK = 1,seq = u+1,ack = w+1」回去,告知服務器端,客戶端已經知道了你準備好關閉了,此時會將客戶端的狀態由 FIN-WAIT-2 置爲 TIME-WAIT,在兩個最長報文段傳輸時間過後,會自動將客戶端的狀態由 TIME-WAIT 置爲 CLOSED。

  6. 服務器端收到消息之後,就將狀態由 LAST-ACK 置爲了 CLOSED,自此,四次揮手全部結束。

題目1:在B向A發送FIN斷開連接的報文後,A發送確認後做了什麼?是馬上斷開嗎?爲什麼要等待2個時間週期?

解答:圖裏很明瞭,並不是馬上斷開,而是等待了2個時間週期。原因有2個:

  • 爲了保證A發送的最後一個ACK報文能到達B,在這兩個時間週期內如果B收不到A的確認,會超時重傳這個FIN+ACK的報文,A就會收到重傳的斷開確認報文,此時A會重傳確認,重新啓動2MSL計時器。假如A不等待2MSL,就不會收到B的重傳報文,也就不會再發送一次確認報文,B就無法按照正常步驟進入CLOSE狀態。
  • A在發送完最後一個ACK報文後,經過2MSL,就可以使本次連接內產生的所有報文都從網絡中消失,在下一個新連接中就不會出現舊的連接報文段。

題目2:主動關閉方和被動關閉方,誰先進入CLOSE狀態?

解答:看圖也可以發現,是被動關閉方先進入CLOSE狀態,原因上一題的答案就可以解釋明白。

題目3:爲何不能三次揮手呢?

解答:首先如果去掉最後一次揮手,那麼服務器端就不知道自己要關閉的報文有沒有傳輸成功,可能半路上就失敗了,但是此時客戶端不知道,導致客戶端一直在等待服務器關閉,但是此時服務器端直接就關閉了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章