計算機網絡中比較中要的無非就是 TCP/IP 協議棧,以及應用層的 HTTP 和 HTTPS 。
前幾天一直炒的的比較火的就是 HTTP/2.0 了,但是其實 HTTP/2.0 早在2015年的時候就已經出來了,並且這個版本是基於 Google 公司的 SPDY 協議發布的,其實說白了就是用的 SPDY 做了一點修改。
好了今天的主題是 TCP 就不過多的介紹 HTTP/2.0 了,以后會專門寫一篇關於 HTTP/2.0 的文章,介紹一下他的新特性。
1.引言
我們都知道 TCP 是位於傳輸層的協議,他還有一個兄弟就是 UDP ,他們兩共同構成了傳輸層。顯然他們之間有很大的區別要不然的話在傳輸層只需要一個就好了。
其中最重要的區別就是一個面向連接另外一個不是,這個區別就導致了他們是否能夠保證穩定傳輸,顯然不面向連接的 UDP 是沒辦法保證可靠傳輸的,他只能靠底層的網絡層和鏈路層來保證。我們都知道網絡層采用的是不可靠的 IP 協議。好吧,網絡層也保證不了可靠傳輸,所以 UDP 保證可靠傳輸只能依靠鏈路層了。
而 TCP 就好說了他不僅僅有底層的鏈路層的支持,還有自己的面向鏈接服務來保證可靠傳輸。當然 TCP也不僅僅就是比 UDP 多了一個可靠傳輸,前面也說到了這只是他們之間一個重要的區別。其實他的三個重要特性就是它們之間的區別。
* 可靠傳輸
* 流量控制
* 擁塞控制
2.可靠傳輸
TCP 主要是確認重傳機制
數據校驗
數據合理分片和排序
流量控制
擁塞控制
依靠來完成可靠傳輸的 , 下面詳細介紹這幾種保證可靠傳輸的方式。
1. 確認和重傳
確認重傳,簡單來說就是接收方收到報文以后給發送方一個 ACK 回復,說明自己已經收到了發送方發過來的數據。如果發送方等待了一個特定的時間還沒有收到接收方的 ACK 他就認為數據包丟了,接收方沒有收到就會重發這個數據包。
好的,上面的機制還是比較好理解的,但是我們會發現一個問題,那就是如果接收方已經收到了數據然后返回的 ACK 丟失,發送方就會誤判導致重發。而此時接收方就會收到冗余的數據,但是接收方怎么能判定這個數據是冗余的還是新的數據呢?
這就涉及到了 TCP 的另外一個機制就是采用序號和確認號,也就是每次發送數據的時候這個報文段里面包括了當前報文段的序號和對上面的報文的確認號,這樣我們的接收方可以根據自己接受緩存中已經有的數據來確定是否接受到了重復的報文段。這時候如果出現上面所說的 ACK 丟失,導致接受重復的報文段時客戶端丟棄這個冗余的報文段。
好現在我們大致了解了確認重傳機制,但是還有些東西還沒有弄清楚,也就是 TCP 真正的實現究竟是怎樣的。
-
確認是每發一個報文段就確認一次還是一次確認多個呢?
-
還有上面所說的發送方等待一個特定的時間,這個時間究竟等多長比較合適?
-
重傳的時候是只重傳那個沒收到的報文還是重傳那個報文段及它以后的報文段?
1.累計確認/單停等協議
這就是我們要解決的第一個問題就是如何確認。這里涉及到兩種確認方式,分別稱為累計確認(捎帶確認)
和 單停等協議
。
單停等協議
用一張圖來快速理解,就是每發送一次數據,就進行一次確認。等發送方收到了 ACK 才能進行下一次的發送。
累計確認
一樣的也是采用的 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 文檔
規定的。
-
第一種情況收到一個期望的有序的數據時,最多延時 500ms 發送一個 ACK 表示該數據及以前的數據都收到了。
-
第二種情況是收到一個期望的有序的數據時,前面的有序數據等待發送 ACK 的時候立即發送一個 ACK 捎帶確認前面那個數據,也就是第一個數據還在延時的時候又來一個那么久兩個一起確認。
-
第三種情況,收到比期望序號大的數據的時候立即發送冗余 ACK ,ACK 確認的值就是中間缺少的第一個序號的值。
-
收到能部分填充或者完全填充中間缺少的數據的,如果這個報文是起始於缺少的數據的低端就立即發送一個 ACK。
好的,那么現在我們可以看到如果出現了三個冗余的 ACK 他只可能是發生了兩次情況三,也就是發送了兩個比期望值大的數據。但是注意出現情況三有兩種可能,一個是丟包,另外一個是亂序到達。
比如說我們現在是數據亂序到達的,我們來看一下。
第一種亂序情況
另外一種亂序
丟包情況
4.數據重傳方式
在我們發現丟包以后我們需要重傳,但是我們重傳的方式也有兩種方式可以選擇分別是 GBN
和 SR
翻譯過來就是 拉回重傳
和 選擇重傳
。好其實我們已經能從名字上面看出來他們的作用方式了,拉回重傳就是哪個地方沒收到那么就從那個地方及以后的數據都重新傳輸,這個實現起來確實很簡單,就是把發送窗口和接受窗口移回去,但是同樣的我們發現這個方式不實用干了很多重復的事,效率低。
那么選擇重傳就是你想到的誰丟了,就傳誰。不存在做無用功的情況。
**結論: TCP 實際上使用的是兩者的結合,稱為選擇確認,也就是允許 TCP 接收方有選擇的確認失序的報文段,而不是累計確認最后一個正確接受的有序報文段。也就是跳過重傳那些已經正確接受的亂序報文段。** ### 2. 數據校驗 數據校驗,其實這個比較簡單就是頭部的一個校驗,然后進行數據校驗的時候計算一遍 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 發送窗口就不會發送數據了。但是有一個問題,這個時候當接收窗口再調大的時候他不會主動通知發送方,這里采用的是發送方主動詢問。
還是畫個圖看的比較直觀:
5. 擁塞控制
擁塞控制一般都是由於網絡中的主機發送的數據太多導致的擁塞,一般擁塞的都是一些負載比較高的路由,這時候為了獲得更好的數據傳輸穩定性,我們必須采用擁塞控制,當然也為了減輕路由的負載防止崩潰。
這里主要介紹兩個擁塞控制的方法,一個是慢開始,另外一個稱為快恢復。
1.慢開始
- 一開始我們不知道網絡中的擁塞情況,我們就發一個數據包
- 如果沒有發生擁塞我們成倍的增加發送的數據的數量。
- 當然我們也不能到無休止的增加,這里有一個慢開始門限,到達門限則加法增加,每次加一。
- 這時候如果遇到了擁塞,我們直接跳到第一步,也就是從頭開始,並且把慢開始門限調整為擁塞時候的數據量的一半再次開始。
2.快恢復
- 一開始我們不知道網絡中的擁塞情況,我們就發一個數據包
- 如果沒有發生擁塞我們成倍的增加發送的數據的數量。
- 當然我們也不能到無休止的增加,這里有一個慢開始門限,到達門限則加法增加,每次加一。
- 這時候如果遇到了擁塞,這里就是唯一和慢開始不一樣的地方,直接從新的慢開始門限加法增長。
3.連接管理
1. 建立連接3次握手
-
客戶端像服務端發起連接,首先向服務端發送一個特殊的報文,這個報文的 SYN 位被置 1 ,然后生成一個隨機的序號填入到 TCP 的頭部。這個報文段稱為 SYN 報文,用於請求連接。
-
服務器接收到客戶端的 SYN 報文以后,也要生成一個特殊的報文段來允許客戶端的接入,這個報文是設置一個自己的初始序號,SYN 設置為 1,ACK 設置為 SYN 報文序號加一。這個報文段稱之為 SYNACK 報文。並且根據 SYN 報文的參數來分配本地變量,但是也是由於這么早的分配變量就有一種 SYN 洪泛攻擊。注意一下,上面的這兩個報文段都沒有數據部分。
-
在客戶端收到 SYNACK 報文段時候需要對客戶端分配變量,然后對服務器的允許進行確認。這時候 SYN 位要置位 0 ,並且可以攜帶數據,也就是這時候是已經開始了數據傳輸的。
那么問題來了,為什么需要序號呢?為什么又是三次握手而不是兩次?以及什么是 SYN 洪泛攻擊?
-
序號存在的目的是為了能否區分多個 TCP 連接,畢竟是一個服務器,多個客戶端,不然各個 TCP 連接就會變得非常混亂。
-
其實我們單方向來看其實就是兩次握手,之所以是三次握手是因為 TCP 是雙工的,中間那次的 SYNACK 其實試一次合並。
-
SYN 洪泛攻擊就是讓客戶端亂遭一些 IP 然后和服務器簡歷 TCP 連接,由於服務器收不到 ACK 但是他分配了變量,導致一直在消耗服務器資源。這個解決方法就是采用 SYNCookie 這個 Cookie 其實就是服務器在發送 SYNACK 的頭部的 seq 序號的值,那么客戶端必須返回一個比 Cookie 大一的 ACK 回來才是正確的,否則不分配變量,也就是變量延時分配。
2.釋放鏈接四次揮手
- 首先客戶端發起終止會話的請求,FIN=1
- 服務器接收到后相應客戶端 ACK=1
- 服務器發送完畢終止會話 FIN=1
- 客戶端回應 ACK=1
這里需要說明一下的是最后的那個長長的 TIME_WAIT 狀態一般是為了客戶端能夠發出 ACK 一般他的值是 1分鍾 或者2分鍾
4.總結
好了,今天真的寫了不少,主要就是把 TCP 的可靠傳輸以及連接管理講清楚了,以及里面的一下細節問題,真的很花時間。然后其他沒有涉及到的就是關於 TCP 的頭並沒有詳細的去分析,這個東西其實也不是很難,但是現在篇幅真的已經很大就先這樣,頭里面的都是固定的不需要太多的理解。