天天看點

計算機網絡 TCP 協定總結TCP 相關知識

TCP 相關知識

TCP/IP 協定占據了網際網路通信的一大半江山,特别像 TCP 這種保障端到端的可靠傳輸更是相當重要,關于它的實作也很複雜,今天介紹下關于 TCP 的相關重要知識。

我們先來看下 TCP 的頭格式:

計算機網絡 TCP 協定總結TCP 相關知識

我們看到有一個

源端口

目的端口

,這 2 個元素再加上 IP 層的

源位址

目的位址

,就可以用來表示 TCP 的某個連接配接了,相當于資料庫裡的唯一辨別。是以當我們發起一個 TCP 連接配接時,會發現如果存在相同的端口在運作時,作業系統是不允許的,隻有這樣才能保證唯一連接配接的存在,避免資料接收混亂。

在上圖中,我們會看到以下幾個元素,它們是 TCP 協定對幾個重要問題的保障:

  • 序列号(seq)

    :資料包的序号,通過序号來确認包的連續性,解決包的亂序問題。
  • 響應号(ack)

    :針對上面字段的确認序号,比如伺服器接收到用戶端的請求,裡面包含了 seq 序号,此時伺服器響應回去時,會進行 ack = seq + 1 的字段設定,表示已接收到的累積資料。
  • Window

    :滑動視窗使用,用來回報接收方接下來能處理的包大小,防止雙方對資料包的處理能力不對等,主要解決了流控問題。
  • TCP Flag

    :TCP 包的類型,用來輔助 TCP 的階段處理,比如 SYN 表示建立連接配接,FIN 表示關閉連接配接。

TCP 狀态流轉

協定之是以會存在,就在于雙方需要互相配合協作,以确定哪個階段該做哪些事情。在 TCP 協定總體劃分為

建立連接配接

資料傳輸

連接配接斷開

這三個過程。這些階段将會涉及對應狀态的流轉:

計算機網絡 TCP 協定總結TCP 相關知識

(

圖檔位址

)

TCP 的三次握手、四次揮手

而在上面的狀态流轉中,最重要,也是經常會提起的關于連接配接建立的

三次握手

以及連接配接斷開的

四次揮手

計算機網絡 TCP 協定總結TCP 相關知識

從上面的流程圖中,我們會發現對于 Client 端來講,經曆了一個請求-确認過程;對于 Server 端來講,也經曆了一個請求-确認過程。這就相當于雙方都已經互相确認過眼神了。是以,三次握手對于連接配接的建立是剛剛好的。

如果我們隻進行 2 次握手就建立連接配接,那麼對于 Server 端來講太容易建立起連接配接了,基本是有用戶端過來,那麼 Server 就要建立起連接配接了。這種情況就會導緻連接配接成本太低,Server 端很容超負載。

至于在關閉連接配接時需要四次揮手,主要是因為 TCP 是

全雙工

的,存在了兩個方向的資料發送與接收,是以需要在兩個方向都進行關閉流程。否則一方關閉了,另一方還在傻傻等待,隻能等待異常逾時結束了。

TIME_WAIT

在四次揮手中,有一個狀态是

TIME_WAIT

,它是主動關閉者在最後會進行的動作,是一個定時設定,在 2*MSL(MSL 表示一個包在網絡環境中的生存時間,一般為 2 分鐘, Linux 裡為 30s)時間過後就會真正的 CLOSED。

之是以不立即關閉,主要為了讓被動關閉方能有足夠的時間接收到最後的 Ack 包,如果沒有接收到,被動方就會重新發送 Fin 包,重新觸發主動方發送最後的 Ack 包。這樣的話,就能盡量保證被動關閉方盡快關閉連接配接了,畢竟主動關閉方需要承擔起主要責任,是以會有 TIME_WAIT 的等待了。

另外一個原因也是怕目前連接配接立馬釋放,有一定機率會立馬被使用到,就有可能産生包的混亂問題了。

TCP 重傳

TCP 發送的包都需要接收方進行一個

Ack

包的響應,如果在一定時間内沒有響應的話,那麼發送方就會認為包未能正确到達,需要進行重傳動作。這就是 TCP 的重傳機制。

TCP 裡的重傳機制會有一個逾時的判斷,這個逾時時間并不是很準确,或者說并不是很标準,畢竟不同的網絡環境,包的到達情況都會是不一樣的。

是以 TCP 會使用一個采樣時間,先記錄了正常情況下一個資料包從發送到響應确認這麼一來一回的時間,即所謂的

RTT

(Round Trip Time) 時間,根據這個時間進行一些公式計算,得到了逾時時間的值:

RTO

(Retransmission TimeOut)

對于重傳機制,還有另外一種觸發機制。上面的情況屬于發送方去探知發送情況,也有另一種情況是接收方能探知的。比如發送方發送了 1, 2, 3 的包,但實際上接收方隻接收到 1 和 3,一直沒能收到 2 這個包,那此時接收方就會連續響應三個 關于 2 的 ack 包。

當發送方收到這麼一個連續的 3 個 ack 包後,就知道需要重傳 2 了,此時就不需要等到 2 的逾時未确認觸發,可以提前的重傳 2 這個包了。

TCP 滑動視窗

重傳機制使得資料包能夠可靠的傳輸,然而如果接收方資料處理能力有限,而發送方未能感應到這種情況,不停的發送資料包,則會增加接收方的壓力,還會導緻網絡擁塞的發生。

為此,TCP 采用

滑動視窗

進行了流量的控制,所謂的滑動視窗即在發送方和接收方各自維護了一個視窗,在這個視窗裡将會維護對應的資料包,以感覺目前的資料處理情況。

在接收方這邊的視窗稱之為

接收視窗

,它具體表示目前所能接收的資料包大小,計算公式為:目前最大可接收緩沖區大小 - 目前已接收的大小,在連接配接建好的開始一般為 65535 位元組。

在計算出可接收大小後,接收方就會将此值設定在 TCP 頭部裡的 Window 字段,然後響應回發送方,發送方也就知道了目前所能允許發送的資料包大小了。

在發送方這邊的視窗稱之為

發送視窗

,按正常邏輯來講,發送視窗維護的是即将要發送的資料,即根據剛剛回報回來的接收視窗大小計算出的發送資料。

但由于一個資料包的發送需要有一個 ACK 響應才算完整流程,是以對于這些“已發送未響應”的資料也應該納入到發送視窗的管理,并且隻有真的 ACK 響應回來,才能繼續下個資料包的準備發送。

](

https://img-blog.csdnimg.cn/img_convert/cb724c4210ceb584d1a7fdde7a4d21c7.png#pic_center)

需要注意的是,如果發送方接收到的 Window 大小為 0,則表示目前的接收方已經無能力處理新的包了,此時發送方就不會再下發資料了,直到接收方發送一個

視窗通告

,才繼續資料的發送。

但此時需要考慮一種情況,就是接收方由于網絡問題沒能将視窗通告送達發送方,那此時發送方就會一直幹等着了.是以對于發送方來講,會啟動視窗探知動作,讓接收方 ACK 它目前的接收視窗大小,如果超過 3 次的探知動作,則直接斷開連接配接了。

TCP 的擁塞控制

在一個複雜的網絡環境中,資料包的傳輸不僅僅隻涉及到端到端的,還可能會出現很多網絡問題,導緻包的堆積,影響了整體的傳輸速度。是以,TCP 協定需要将網絡的阻塞情況考慮進來,避免加劇。這就是 TCP 的擁塞控制。

為此,TCP 協定抽象出了

擁塞視窗

(cwnd)的概念,它會根據目前的網絡擁塞程度進行動态的調整。由于加入了擁塞情況的考慮,前面我們提到過的發送視窗則不能僅僅隻考慮接收視窗這個因素了,需要進行

min(擁塞視窗,接收視窗)

的選擇了。

由此可見,擁塞視窗的計算很重要,它将決定了資料包的發送大小。而關于擁塞視窗的計算,它将在幾個場景裡會涉及到,下面我們一一來分析。

MTU 和 MSS

在分析擁塞視窗的具體場景之前,我們先來看看擁塞視窗的基本機關:MSS。MSS 表示 網絡傳輸資料的最大值,如果 MSS 加上標頭大小,則表示網絡傳輸最大封包:MTU 了。

在 Internet 這種網際網路中,一般 MTU 定義為 576 位元組,減去 TCP、IP 的標頭 40 位元組,則可以得到 MSS = 536 位元組的值;而在以太網這種區域網路裡,一般 MTU 會大點:1500 位元組,MSS 為 1460 位元組。

慢啟動

當連接配接建立完畢,開始傳輸資料時,TCP 協定規定不能一開始就發送大尺寸的資料包,這樣避免了網絡環境有問題時,新加入的連接配接加劇了擁塞狀況。是以,對于新加入的連接配接而言,需要一點一點的增大資料量,這就是所謂的

慢啟動

其中,慢啟動涉及的擁塞視窗計算過程如下:

  • 剛開始建立好連接配接時,擁塞視窗 = 1
  • 每當接收到一個 ACK 包時,擁塞視窗 = 擁塞視窗 + 1,此時呈線性增加。
  • 每當經過一個 RTT,擁塞視窗 = 擁塞視窗 * 2,此時呈指數上升趨勢。

擁塞避免

從慢啟動的算法來看,每經過一個 RTT 後,擁塞視窗 的增長速度将會變得很厲害,如果沒有進行限制的話,那麼很快就會占滿帶寬了。是以, TCP 協定使用了一個叫慢啟動門限(ssthresh)的變量(一般取 65535 位元組)。當 cwnd 超過該限制後,就會進入所謂的

擁塞避免

階段了。

在擁塞避免階段,擁塞視窗的計算過程如下:

  • 每接收到一個 ACK 包時,擁塞視窗 = 擁塞視窗 + 1/擁塞視窗
  • 每當經過一個 RTT,擁塞視窗 = 擁塞視窗 + 1

從上面的算法可以看出,進入擁塞避免階段後,資料包的發送大小将呈線性增加了。通過這樣的方式,使得 TCP 的傳輸在前期很快,然後再慢慢降下來,達到網絡最佳值。

擁塞發生

前面都是在避免擁塞的發生去動态調整擁塞視窗(cwnd)的,然而按照線性增長的趨勢,始終會導緻網絡擁塞的。當發送擁塞(一般隻要丢包,需要重傳資料包就認為發送了擁塞)時,TCP 協定該如何處理呢?此處也算是 TCP 協定比較複雜的地方,因為它在不斷的改進,也衍生出了很多版本,下面我們來看看這些不同版本的差別和處理吧。

Tahoe 版本

Tahoe 版本是 TCP 的最早版本,當它發現需要進行重傳動作,即觸發了 RTO 逾時或發送方收到三個重複 ACK 包時,此時會進行的動作為:

  • sshthresh = cwnd /2
  • cwnd 重置為 1
  • 重回慢啟動階段

Reno 版本

Reno 版本進行的動作為:

  • cwnd = sshthresh + 3 * MSS (将 3 個重複 ACK 考慮進去)
  • 進入快速恢複階段

其中,快速恢複階段的步驟如下:

  • 當重傳的包發出去後,收到了重傳包的 ACK 後,cwnd = cwnd + 1
  • 當收到新的資料包的 ACK 後,此時快速恢複過程已結束,則 cwnd = sshthresh,然後重回擁塞避免階段

NewReno 版本

NewReno 是對 Reno 的改進,主要是優化了快速恢複階段,在 Reno 版本中,所考慮的都是一個包的丢失情況。然而,在實際情況中,一次資料視窗的發送,是有可能出現很多資料包丢失情況的。

這樣的話,就會觸發多次的 cwnd 和 ssthresh 減半動作,一旦 cwnd 降到小于 3 時,即發送視窗會出現小于 3 的情形,此時将再也觸發不了 3 次快速重傳動作了,隻能依賴 RTO 逾時,而一般 RTO 的值是比較大(太小會經常觸發重傳)的,此時整個傳輸速度将會大大降低。

是以 NewReno 會在收到所有資料包的确認後才結束快速恢複階段,這樣 cwnd 和 sshthresh 就不會輕易被降低了。

NewReno 主要是使用了一個 recover 變量,作為目前資料視窗中,可能丢包的最大序号。即如果有丢包情況産生,并且大于目前的 recover 值,則會更新該值。

當收到接收方的 ack 後,會進行 ack_seq 的判斷,如果 ack_seq > recover,此時就可以結束快速恢複階段了;如果 ack_seq < recover,則意味着多包丢失,還不能結束快速恢複階段。通過這樣的控制,來提高了整個的吞吐量。

Nagle 算法

下面我們來看看 TCP 裡的其他經典算法:Nagle。Nagle 算法是一種通過減少小資料包的發送來提高 TCP 效率的機制。它會把多個小資料包合并到一個片段,并且等待滿足一定條件後,再一起發送過去。具體的觸發條件是:

  • 如果包長度達到 MSS,則允許發送
  • 如果包含 FIN,則允許發送
  • 如果設定了 TCP_NODELAY,則允許發送
  • 未設定 TCP_CORK 選項時,若所有發出去的小資料包(包長度小于 MSS)均被确認,則允許發送

當上述條件都未滿足,但發生了逾時(一般為 200ms),則立即發送。

對于 TCP 協定來講,預設會啟用 Nagle 算法,降低網絡負載,減少網絡擁塞,提高網絡吞吐。

Delay Ack

Delay Ack 指延遲發送 Ack。在 TCP 的确認機制裡,可以在通信過程中不對每一個 TCP 資料包進行單獨的 ACK 包響應,而是在傳輸資料時,順便把 ACK 資訊随資料包一起發送,這樣可以提高使用率。

如果在一定時間内(一般 40 ms)沒有資料包要發送,那麼就會單獨的進行 ACK 包響應。這個過程就被稱為 Delay Ack 了。

粘包與拆包

TCP 是面向位元組流的傳輸,它會根據接收方的包處理能力以及目前網絡的擁塞情況來一部分一部分的加載資料發送,再加上有 Nagle 這種整合小資料包的算法存在。是以對于接收方來講,接收到的資料有可能是粘合在一起的,也有可能是被拆分開的,即所謂的粘包和拆包。

對于粘包和拆包現象,常用的解決方案有:

  • 在包的首部添加目前要傳輸的資料包的長度,讓接收方根據長度去切割。
  • 将資料包封裝為固定長度,不夠的補 0,讓接收方按固定長度解析。
  • 人為的給資料包添加邊界,比如在資料包結尾添加特殊字元,當解析到特殊字元時,接收方就認為讀取到了一個完整有意義的資料段了。

繼續閱讀