TCP 詳解

.引言

咱們都知道 TCP 是位於傳輸層的協議,他還有一個兄弟就是 UDP ,他們兩共同構成了傳輸層。顯然他們之間有很大的區別要否則的話在傳輸層只須要一個就行了。緩存

其中最重要的區別就是一個面向鏈接另一個不是,這個區別就致使了他們是否可以保證穩定傳輸,顯然不面向鏈接的 UDP 是沒辦法保證可靠傳輸的,他只能靠底層的網絡層和鏈路層來保證。咱們都知道網絡層採用的是不可靠的 IP 協議。好吧,網絡層也保證不了可靠傳輸,因此 UDP 保證可靠傳輸只能依靠鏈路層了。服務器

而 TCP 就好說了他不只僅有底層的鏈路層的支持,還有本身的面向連接服務來保證可靠傳輸。固然 TCP也不只僅就是比 UDP 多了一個可靠傳輸,前面也說到了這只是他們之間一個重要的區別。其實他的三個重要特性就是它們之間的區別。網絡

* 可靠傳輸
* 流量控制
* 擁塞控制app

2.可靠傳輸

TCP 主要是確認重傳機制 數據校驗 數據合理分片和排序 流量控制 擁塞控制依靠來完成可靠傳輸的 , 下面詳細介紹這幾種保證可靠傳輸的方式。ide

1. 確認和重傳

確認重傳,簡單來講就是接收方收到報文之後給發送方一個 ACK 回覆,說明本身已經收到了發送方發過來的數據。若是發送方等待了一個特定的時間尚未收到接收方的 ACK 他就認爲數據包丟了,接收方沒有收到就會重發這個數據包。性能

好的,上面的機制仍是比較好理解的,可是咱們會發現一個問題,那就是若是接收方已經收到了數據而後返回的 ACK 丟失,發送方就會誤判致使重發。而此時接收方就會收到冗餘的數據,可是接收方怎麼能斷定這個數據是冗餘的仍是新的數據呢?優化

這就涉及到了 TCP 的另一個機制就是採用序號和確認號,也就是每次發送數據的時候這個報文段裏面包括了當前報文段的序號和對上面的報文的確認號,這樣咱們的接收方能夠根據本身接受緩存中已經有的數據來肯定是否接受到了重複的報文段。這時候若是出現上面所說的 ACK 丟失,致使接受重複的報文段時客戶端丟棄這個冗餘的報文段。spa

好如今咱們大體瞭解了確認重傳機制,可是還有些東西尚未弄清楚,也就是 TCP 真正的實現到底是怎樣的。翻譯


  1. 確認是每發一個報文段就確認一次仍是一次確認多個呢?
  2. 還有上面所說的發送方等待一個特定的時間,這個時間究竟等多長比較合適?
  3. 重傳的時候是隻重傳那個沒收到的報文仍是重傳那個報文段及它之後的報文段?
    1.累計確認/單停等協議

這就是咱們要解決的第一個問題就是如何確認。這裏涉及到兩種確認方式,分別稱爲累計確認(捎帶確認) 和 單停等協議 。設計

單停等協議


f4a9fe0601a95b82a842c2beeb36b066.jpeg


用一張圖來快速理解,就是每發送一次數據,就進行一次確認。等發送方收到了 ACK 才能進行下一次的發送。

累計確認


1dee28e860eea80bcee6a61cf9b2712b.jpeg


同樣的也是採用的 ACK 機制,可是注意一點的是,並不是對於每個報文段都進行確認,而僅僅對最後一個報文段確認,捎帶的確認了上圖中的 203 號及之前的報文。
總結:從上面能夠看到累計確認的效率更加高,首先他的確認包少一些那麼也就是在網絡中出現的大部分是須要傳輸的數據,而不是一半的數據一半的 ACK ,而後咱們在第二張圖中能夠看到咱們是能夠連續發送多個報文段的(究竟一次性能發多少這個取決於發送窗口,而發送窗口又是由接受窗口和擁塞窗口一塊兒來決定的。),一次性發多個數據會提升網絡的吞吐量以及效率這個能夠證實,比較簡單這裏再也不贅述!

結論:顯然怎麼看都是後者比較有優點,TCP 的實現者天然也是採用的累計確認的方式!

2. 超時時間計算

上文中的那個特定的時間就是超時時間,爲何有這個值呢? 其實在發送端發送的時候就爲數據啓動了一個定時器,這個定時器的初始值就是超時時間。

超時時間的計算其實有點麻煩,主要是咱們很難肯定一個肯定的值,太長則進行了無心義的等待,過短就會致使冗餘的包。TCP 的設計者們設計了一個計算超時時間的公式,這個公式概念比較多,有一點點麻煩,不過不要緊咱們一點點的來。

首先咱們本身思考如何設計一個超時時間的計算公式,超時時間通常確定是和數據的傳輸時間有關係的,他必然要大於數據的往返時間(數據在發送端接收端往返一趟所用的時間)。好,那麼咱們就從往返時間下手,但是又有一個問題就是往返時間並非固定的咱們有如何肯定這個值呢?天然咱們會想到咱們能夠取一小段時間的往返時間的平均值來表明這一時間點的往返時間,也就是微積分的思想!

好了咱們找到了往返時間(RTT),接下來的超時時間應該就是往返時間再加上一個數就能獲得超時時間了。這個數也應該是動態的,咱們就選定爲往返時間的波動差值,也就是相鄰兩個往返時間的差。

下面給出咱們所預估的超時時間(TimeOut)公式:

TimeOut = AvgRTT2 + | AvgRTT2 - AvgRTT1 |

很好,看到這裏其實你已經差很少理解了超時時間的計算方式了,只不過咱們這個公式不夠完善,可是思路是對的。咱們這時候來看看 TCP 的實現者們採用的方式。

RTT_New = (1-a)RTT_Current + a*Avg_RTT (計算平均 RTT,a 一般取0.125)
DevRTT = (1-b)DevRTT + b|RTT_New - Avg_RTT|  (計算差值,b 一般取0.25)
TimeOut = RTT_New + 4*DevRTT (計算超時時間)

好的,這就是 TCP 實現的超時時間的方式,可是在實際的應用中並非一直採用的這種方式。假如說咱們如今網絡狀態很是的差,一直在丟包咱們根本不必這樣計算,而是採用直接把原來的超時時間加倍做爲新的超時時間。

總結:好的如今咱們知道了在兩種狀況下的超時時間的計算方式,正常的狀況下咱們採用的上面的比較複雜的計算公式,也就是 RTT+波動值 不然直接加倍

3. 快速重傳

上面咱們看到在發送方等待一個超時重傳時間後會開始重傳,可是咱們計算的超時重傳時間也不定就很準,也就是說咱們常常乾的一件事就會是等待,並且通常等的時間還挺長。那麼可不能夠優化一下呢?

固然,在 TCP 實現中是作了優化的,也就是這裏說到的快速重傳機制。他的原理就是在發送方收到三個冗餘的 ACK 的時候,就開始重傳那個報文段。那麼爲何是三個冗餘的 ACK 呢?注意三個冗餘的 ACK 實際上是四個 ACK 。咱們先了解一下發送 ACK 策略,這個是 RFC 5681 文檔 規定的。


  1. 第一種狀況收到一個指望的有序的數據時,最多延時 500ms 發送一個 ACK 表示該數據及之前的數據都收到了。
  2. 第二種狀況是收到一個指望的有序的數據時,前面的有序數據等待發送 ACK 的時候當即發送一個 ACK 捎帶確認前面那個數據,也就是第一個數據還在延時的時候又來一個那麼久兩個一塊兒確認。
  3. 第三種狀況,收到比指望序號大的數據的時候當即發送冗餘 ACK ,ACK 確認的值就是中間缺乏的第一個序號的值。
  4. 收到能部分填充或者徹底填充中間缺乏的數據的,若是這個報文是起始於缺乏的數據的低端就當即發送一個 ACK。

好的,那麼如今咱們能夠看到若是出現了三個冗餘的 ACK 他只多是發生了兩次狀況三,也就是發送了兩個比指望值大的數據。可是注意出現狀況三有兩種可能,一個是丟包,另一個是亂序到達。
好比說咱們如今是數據亂序到達的,咱們來看一下。

第一種亂序狀況

a81c342773aff364b6e62a0f8126544a.jpeg


另一種亂序

0586285a6d77ed097b45e67533304774.jpeg


丟包狀況

efa0b20c176f048e87322b8e55ba16d6.jpeg


結論: 很顯然咱們能夠看到,若是發生了亂序有可能會出現三次冗餘 ACK,可是若是發現了丟包必然會有三次冗餘 ACK 發生,只是 ACK 數量可能更多可是不會比三次少

4.數據重傳方式

在咱們發現丟包之後咱們須要重傳,可是咱們重傳的方式也有兩種方式能夠選擇分別是 GBN 和SR 翻譯過來就是 拉回重傳 和 選擇重傳 。好其實咱們已經能從名字上面看出來他們的做用方式了,拉回重傳就是哪一個地方沒收到那麼就從那個地方及之後的數據都從新傳輸,這個實現起來確實很簡單,就是把發送窗口和接受窗口移回去,可是一樣的咱們發現這個方式不實用幹了不少重複的事,效率低。

那麼選擇重傳就是你想到的誰丟了,就傳誰。不存在作無用功的狀況。

結論: TCP 實際上使用的是二者的結合,稱爲選擇確認,也就是容許 TCP 接收方有選擇的確認失序的報文段,而不是累計確認最後一個正確接受的有序報文段。也就是跳太重傳那些已經正確接受的亂序報文段。

2. 數據校驗


71693307d66d54773c0834b2b50dd284.png


數據校驗,其實這個比較簡單就是頭部的一個校驗,而後進行數據校驗的時候計算一遍 checkSum 比對一下。

3. 數據合理分片和排序

在 UDP 中,UDP 是直接把應用層的數據往對方的端口上 「扔」 ,他基本沒有任何的處理。因此說他發給網絡層的數據若是大於1500字節,也就是大於MTU。這個時候發送方 IP 層就須要分片。把數據報分紅若干片,使每一片都小於MTU.而接收方IP層則須要進行數據報的重組。這樣就會多作許多事情,而更嚴重的是 ,因爲UDP的特性,當某一片數據傳送中丟失時 , 接收方便沒法重組數據報,將致使丟棄整個UDP數據報。

而在 TCP 中會按MTU合理分片,也就是在 TCP 中有一個概念叫作最大報文段長度(MSS)它規定了 TCP 的報文段的最大長度,注意這個不包括 TCP 的頭,也就是他的典型值就是 1460 個字節(TCP 和 IP 的頭各佔用了 20 字節)。而且因爲 TCP 是有序號和確認號的,接收方會緩存未按序到達的數據,根據序號從新排序報文段後再交給應用層。

4. 流量控制

流量控制通常指的就是在接收方接受報文段的時候,應用層的上層程序可能在忙於作一些其餘的事情,沒有時間處理緩存中的數據,若是發送方在發送的時候不控制它的速度頗有可能致使接受緩存溢出,致使數據丟失。

相對的還有一種狀況是因爲兩臺主機之間的網絡比較擁塞,若是發送方仍是以一個比較快的速度發送的話就可能致使大量的丟包,這個時候也須要發送方下降發送的速度。

雖然看起來上面的兩種狀況都是因爲可能致使數據丟失而讓發送主機下降發送速度,可是必定要把這兩種狀況分開,由於前者是屬於流量控制 然後者是 擁塞控制 ,那將是咱們後面須要討論的事情。不要把這兩個概念混了。

其實說到流量控制咱們就不得不提一下滑動窗口協議,這個是流量控制的基礎。因爲 TCP 鏈接是一個全雙工的也就是在發送的時候也是能夠接受的,因此在發送端和接收端同時維持了發送窗口和接收窗口。這裏爲了方便討論咱們就按照單方向來討論。

接收方維持一個接受窗口,發送方一個發送窗口。發送的時候要知道接受窗口還有多少空間,也就是發送的數據量不能超過接受窗口的大小,不然就溢出了。而當咱們收到一個接收方的 ACK 的時候咱們就能夠移動接受窗口把那些已經確認的數據滑動到窗口以外,發送窗口同理把確認的移出去。這樣一直維持兩個窗口大小,當接收方不能在接受數據的時候就把本身的窗口大小調整爲 0 發送窗口就不會發送數據了。可是有一個問題,這個時候當接收窗口再調大的時候他不會主動通知發送方,這裏採用的是發送方主動詢問。

仍是畫個圖看的比較直觀:

313f90bbd2ae29edb3740b4a92271d5f.png


5. 擁塞控制

擁塞控制通常都是因爲網絡中的主機發送的數據太多致使的擁塞,通常擁塞的都是一些負載比較高的路由,這時候爲了得到更好的數據傳輸穩定性,咱們必須採用擁塞控制,固然也爲了減輕路由的負載防止崩潰。

這裏主要介紹兩個擁塞控制的方法,一個是慢開始,另一個稱爲快恢復。

1.慢開始


  1. 一開始咱們不知道網絡中的擁塞狀況,咱們就發一個數據包
  2. 若是沒有發生擁塞咱們成倍的增長髮送的數據的數量。
  3. 固然咱們也不能到無休止的增長,這裏有一個慢開始門限,到達門限則加法增長,每次加一。
  4. 這時候若是遇到了擁塞,咱們直接跳到第一步,也就是從頭開始,而且把慢開始門限調整爲擁塞時候的數據量的一半再次開始。

2.快恢復


  1. 一開始咱們不知道網絡中的擁塞狀況,咱們就發一個數據包
  2. 若是沒有發生擁塞咱們成倍的增長髮送的數據的數量。
  3. 固然咱們也不能到無休止的增長,這裏有一個慢開始門限,到達門限則加法增長,每次加一。
  4. 這時候若是遇到了擁塞,這裏就是惟一和慢開始不同的地方,直接重新的慢開始門限加法增加。


d3116c65bec909f47ce2e7e0541b1495.jpeg


3.鏈接管理

1. 創建鏈接3次握手


  1. 客戶端像服務端發起鏈接,首先向服務端發送一個特殊的報文,這個報文的 SYN 位被置 1 ,而後生成一個隨機的序號填入到 TCP 的頭部。這個報文段稱爲 SYN 報文,用於請求鏈接。
  2. 服務器接收到客戶端的 SYN 報文之後,也要生成一個特殊的報文段來容許客戶端的接入,這個報文是設置一個本身的初始序號,SYN 設置爲 1,ACK 設置爲 SYN 報文序號加一。這個報文段稱之爲 SYNACK 報文。而且根據 SYN 報文的參數來分配本地變量,可是也是因爲這麼早的分配變量就有一種 SYN 洪泛***。注意一下,上面的這兩個報文段都沒有數據部分。
  3. 在客戶端收到 SYNACK 報文段時候須要對客戶端分配變量,而後對服務器的容許進行確認。這時候 SYN 位要置位 0 ,而且能夠攜帶數據,也就是這時候是已經開始了數據傳輸的。


53c978964aace38424d9f15e94bb89ab.jpeg


那麼問題來了,爲何須要序號呢?爲何又是三次握手而不是兩次?以及什麼是 SYN 洪泛***?


  1. 序號存在的目的是爲了可否區分多個 TCP 鏈接,畢竟是一個服務器,多個客戶端,否則各個 TCP 鏈接就會變得很是混亂。
  2. 其實咱們單方向來看其實就是兩次握手,之因此是三次握手是由於 TCP 是雙工的,中間那次的 SYNACK 其實試一次合併。
  3. SYN 洪泛***就是讓客戶端亂遭一些 IP 而後和服務器簡歷 TCP 鏈接,因爲服務器收不到 ACK 可是他分配了變量,致使一直在消耗服務器資源。這個解決方法就是採用 SYNCookie 這個 Cookie 其實就是服務器在發送 SYNACK 的頭部的 seq 序號的值,那麼客戶端必須返回一個比 Cookie 大一的 ACK 回來纔是正確的,不然不分配變量,也就是變量延時分配。

2.釋放連接四次揮手


  1. 首先客戶端發起終止會話的請求,FIN=1
  2. 服務器接收到後相應客戶端 ACK=1
  3. 服務器發送完畢終止會話 FIN=1
  4. 客戶端迴應 ACK=1


dfe89dfc5616f155a657bca1100bd442.jpeg


這裏須要說明一下的是最後的那個長長的 TIME_WAIT 狀態通常是爲了客戶端可以發出 ACK 通常他的值是 1分鐘 或者2分鐘

4.總結

好了,今天真的寫了很多,主要就是把 TCP 的可靠傳輸以及鏈接管理講清楚了,以及裏面的一下細節問題,真的很花時間。而後其餘沒有涉及到的就是關於 TCP 的頭並無詳細的去分析,這個東西其實也不是很難,可是如今篇幅真的已經很大就先這樣,頭裏面的都是固定的不須要太多的理解。

相關文章
相關標籤/搜索