TCP學習

winterYANGWT發表於2024-03-08

本文施工狀態

未完工,一些具有說明意味的圖片未加入,流量控制和擁塞控制沒有提及,與UDP的對比也沒有。

本文是什麼

在之前的合集文章中,我們瞭解了UDP並簡單說明了一下什麼是連線。UDP格式簡單,傳送簡便,但是並不是可靠的傳輸。而在本文中將會對TCP進行介紹。其中包括TCP的報文格式,TCP的連線的建立和關閉,TCP的丟包重新傳輸,擁塞控制和流量控制等內容,並使用Wireshark進行實際的介紹。

正文

TCP

TCP是傳輸控制協議(Transmission Control Protocol)的縮寫。從RFC中為TCP的定義我們知道,TCP為應用提供可靠,有序,基於位元組流的服務。其可靠性在於TCP能夠感知到一些資料包在網路中丟失,並安排重新傳送,知道這個資料確實傳輸到對端。其有序性在於TCP提供了資料的序列號,透過序列號就可以將收到的資料包進行排序。其基於位元組流也和UDP也有著區別,UDP面向資料包,發出的一個資料就是一段完整的報文;而TCP則可以將一個報文看作位元組流,可以分成多段,一段一段地傳送過去。

TCP報文格式

在具體說明TCP其性質的實現之前,我們需要提前瞭解一下TCP報文的格式與內容。

TCP學習
  • 源埠:16位長度,與UDP一樣,為傳送方的埠號。
  • 目的埠:16位長度,與UDP一樣,為接收方的埠號。
  • 序列號:32位長度,可以簡單理解為本次資料載荷首位元組在整體資料的位置,但其實最開始是一個隨機值。比如初始隨機值為100,本次要傳送的資料的首位元組在整體資料的位置是1000,則序列號是1100=100+1000。
  • 確認號:32位長度,表示希望收到下一次資料的序列號,與對端的序列號有關。比如我們收到了一個資料,序列號為2000,長度為1000。則我們希望下一次收到資料的序列號為3000,同時也表示序列號在這之前的資料已經被我們收到。
  • 資料偏移位置:4位長度,單位為4位元組。因為TCP的報文長度是一個可變的值,資料偏移位置指定了資料載荷的開始的位置,其實也就是TCP報文頭的大小。假設這個值為0x5,也就說明報文頭的長度為20=5*4位元組。
  • 保留位:4位長度。
  • 控制位 8位長度,目前共8個控制位,分別是:
    • CWR:1位長度,置1表示減少擁塞視窗大小。
    • ECE:1位長度,置1表示ECN-Echo。
    • URG:1位長度,置1表示有緊急資訊且緊急指標是有效的。
    • ACK:1位長度,置1表示確認號是有效的。
    • PSH:1位長度,置1表示將緩衝區的所有資料都推給應用。
    • RST:1位長度,置1表示連線出現了問題,需要進行重置。
    • SYN:1位長度,置1表示進行連線。
    • FIN:1位長度,置1表示連線結束。
  • 視窗大小:16位長度,滑動視窗的大小。
  • 校驗和:16位長度,與UDP一樣,檢驗資料是否合法。
  • 緊急指標:16位長度,如果有緊急訊息,指示緊急訊息在資料載荷的位置。
  • 可選選項:TCP的一些選項,後面使用填充位補齊為4位元組的倍數。

TCP連線的建立,三次握手

TCP的三次握手是一個經常被提起的事物,但很多朋友對此的理解可能比較生硬,讓我們舉一個生活中的例子。在我們打電話的時候,我們第一句會說 “你好,你聽得見嗎?”。如果對方回答了“你好,我聽得見”,這就說明我們的聲音能傳給對方。否則意味著對方什麼都沒聽到,我們的聲音沒法傳過去。我們可能需要再次詢問或者重新撥打對方的電話。在確認對方能聽見我們後,我們也會告訴對方”我也聽得見你“,對方在聽到這句話後也知道他的聲音能夠傳到我們這裡。這樣,雙方就可以正常開始溝通了。
如果你能理解上面的例子,那就讓我們開始正式介紹TCP連線的建立過程吧。

TCP學習
  1. 一開始A是連線關閉的狀態,B是正在監聽的狀態。
  2. A傳送一個<SEQ=100><CTL=SYN>的報文。A的狀態變為SYN-SENT,表示A此時已將連線請求傳送。
  3. B收到了A發來的連線請求。返回<SEQ=300><ACK=101=100+1><CTL=SYN, ACK>的報文。B的狀態狀態變為SYN-RECEIVED,表示B已經收到了連線請求,並且同樣發起連線建立的請求。
  4. A收到了B發來的報文。返回<SEQ=101><ACK=301=300+1><CTL=ACK>的報文。A的狀態變為ESTABLISHED,說明A->B的傳輸連線已經建立。
  5. B收到回覆。B的狀態變為ESTABLISHED,表示B->A的連線已經建立。
  6. 雙端的連線都完成了,現在A開始傳送資料,可以看到傳送的報文中已經帶上了資料載荷。
  7. 雙方連線建立完成,A傳送的資料包文帶上了真正資料載荷。

下圖為透過HTTP(HTTP使用TCP)來展示了真實的TCP的連線建立過程,各位朋友可以根據上面所展示的過程與實際抓包結果進行聯絡。
image
同時,我們還注意到了一點,握手中傳送的確認報文中ACK都是對方序列號+1。一般來說,收到了普通報文中如果沒有資料載荷,則ACK是不會變的。而在收到SYN報文和FIN報文的時候,都會將ACK+1。

TCP連線的結束,四次揮手

再說完TCP連線的建立後,我們就要就要開始談談TCP連線的結束了。剛建立就要結束,或許是太快了一點。但這也是因為TCP連線的結束與建立有著一些相同之處和不同之處。我們透過對比的方法能夠更好地理解這兩個過程。關於連線中的那些事,我們後面再慢慢來說。

  1. 一開始,雙方都是處於連線的狀態中。
  2. 這裡假設A已經發完資料了,於是A發出了一個<SEQ=100><ACK=300><CTL=FIN, ACK>的報文。A的狀態也變為FIN-WAIT-1,表示自己已經沒有多餘的資料了,請求關閉連線。
  3. B收到A的報文,返回<SEQ=300><ACK=101=100+1><CTL=ACK>的報文。B進入CLOSE-WAIT狀態,而A在收到確認後狀態變為FIN-WAIT。這時候連線是一種半開狀態,A無法向B傳送資料,因為自己主動結束了傳送,而B還能繼續向A傳送資料。
  4. 過了一段時間,B的資料也發完了,於是它也傳送<SEQ=300><ACK=101><CTL=FIN, ACK>報文。自身進入LAST-ACK狀態,這是它最後一次發生ACK了(其實也不一定,如果第一次ACK丟失了,它還得重新傳送)。
  5. A收到報文,返回<SEQ=101><ACK=301><CTL=ACK>報文,表示自己已經收到了關閉請求。B收到這個報文後就會進入CLOSED狀態,表示已經關閉連線。A則會先進入TIME-WAIT狀態,因為如果A傳送的報文丟失了,他收到B的重傳報文後還要再發一遍,可不能立即關閉了。在等待兩倍最大傳輸時間之後,就認為對面應該是收到了確認報文了,於是就將自己關閉也進入CLOSED狀態。

重傳

我們說TCP是一個具有可靠性的傳輸協議,不是說TCP使用了什麼方法將整個網路質量提高了而不會丟包(它也做不到)。而是TCP建立了重傳機制,丟包是可能發生的,但是TCP在發現丟包後會將這個包重新發過去,直到對方確實收到(TCP:包可能會遲到,但不會缺席)。
於是就出現了一個問題,TCP是如何知道有丟包的呢?其實方法很簡單,我們只要在包發出去的時候記錄發出的時間並設定一個計時器,如果計時器超過了一個閾值並且沒收到確認報文,就認為這個包丟了,需要進行重傳。這個方法又叫做超時重傳。
解決了如何發現丟包,現在又有了一個問題。這個超時閾值又該怎麼設定呢?如果超時閾值設定太短,那麼很容易發生重傳,整個鏈路上都是重複的包,效率實在是太低了!那麼我們把時間調長一些呢?如果超時閾值太長,那麼確認到丟包要進過很久,這個時候鏈路上又沒有包了,效率實在是太低了!進過考量,我們需要這個超時閾值比實際的報文往返時間稍微大一點。TCP會統計每次包的往返時間,並進行一個取均值的方案(樣本估計中的均值估計)。同時為了防止測量的報文往返時間波動較大,採用了滑動平均的方式進行平滑處理。

\[估計時間 = (1-\alpha)*估計時間+\alpha * 單次測量時間 \]

在超時重傳中,當發現重傳的包再次丟包後,意味著網路此時比較堵塞,於是會進行一個將超時時間加倍的方案來防止將本就不堪重負的鏈路再次雪上加霜。在Linux中,最長的超時時間可能會到達2分鐘,這或許有點太長了。於是TCP又加入了快速重傳機制,當收到多個(實際上3個)相同的確認報文後就進行重傳。我們舉一個例子。

  1. A傳送了4個包<SEQ=100>、<SEQ=200>、<SEQ=300>、<SEQ=400>。其中<SEQ=100>的包丟失了,但剩下三個包都順利到達。
  2. B收到三個包,對於<SEQ=200>,B說我收到了這個資料並進行儲存,但是我沒收到前面的,下次還是希望你傳送<SEQ=100>的包。其他兩個包也是同樣的道理。於是B傳送3個<ACK=100>。
  3. A收到這3個相同的確認報文,很明顯知道<SEQ=100>的包丟失了。這時候就需要開始進行重傳。

相關文章