TCP 詳解


TCP 詳解

參考:https://blog.csdn.net/sinat_36629696/article/details/80740678

https://www.jianshu.com/p/ef892323e68f

https://mp.weixin.qq.com/s/LUtk6u_zv0w8g8GIGWEuCw

TCP協議

TCP協議全稱: 傳輸控制協議,顧名思義,就是要對數據的傳輸進行一定的控制.

先來看看它的報頭

img

每部分的含義和說明

  • 源端口和目的端口

    各占16位,端口是傳輸層與應用層的服務接口,傳輸層的復用和分用功能都要通過端口才能實現。

  • 序號

    占 32位,TCP 連接中傳送的數據流中的每一個字節都編上一個序號(seq),序號字段的值則指的是本報文段所發送的數據的第一個字節的序號。 Seq 就是 Sequence Number 即序號,它是用來解決亂序問題的

  • 確認號

    占 32位,是期望收到對方的下一個報文段的數據的第一個字節的序號(ACK)。 ACK 就是 Acknowledgement Numer 即確認號,它是用來解決丟包情況的,告訴發送方這個包我收到啦。

  • 數據偏移/首部長度

    占 4位,它指出 TCP 報文段的數據起始處距離 TCP 報文段的起始處有多遠,“數據偏移”的單位是 32 位字

  • 保留

    占 6 位,保留為今后使用,但目前應置為 0,六位分別是URG,ACK,PSH,RST,SYN,FIN。

    URG: 標識緊急指針是否有效 ,表示此報文段中有緊急數據,應盡快傳送(相當於高優先級的數據)
    ACK: 標識確認序號是否有效,表示只有當 ACK有效時確認號字段才有效
    PSH: 提示接收端應用程序讀取tcp緩沖區數據,表示接收應用進程應盡快地讀取緩存區數據,而不再等到整個緩存都填滿了后再讀取
    RST: 請求重新建立連接. 我們把含有RST標識的報文稱為復位報文段,表示 TCP 連接中出現嚴重差錯,必須釋放連接,然后再重新建立運輸連接
    SYN: 請求建立連接. 我們把含有SYN標識的報文稱為同步報文段,表示這是一個連接請求或連接接受報文
    FIN: 請求釋放連接. 我們把含有FIN標識的報文稱為結束報文段,表示此報文段的發送端的數據已發送完畢,並要求釋放運輸連接

  • 檢驗和

    占 16為,檢驗和字段檢驗的范圍包括首部和數據這兩部分,由發送端填充,,檢驗形式有CRC校驗等。 如果接收端校驗不通過,則認為數據有問題。

  • 緊急指針

    占 16 位, 用來標識哪部分數據是緊急數據,指出在本報文段中緊急數據共有多少個字節(緊急數據放在本報文段數據的最前面)

特點

TCP 是面向連接的傳輸層協議 每一條 TCP 連接只能有兩個端點(endpoint),每一條 TCP 連接只能是點對點的(一對一) TCP 提供可靠交付的服務 TCP 提供全雙工通信 面向字節流

連接管理機制

正常情況下, tcp需要經過三次握手建立連接,四次揮手斷開連接

三次握手

第一次:
客戶端 - - > 服務器 此時服務器知道了客戶端要建立連接了
第二次:
客戶端 < - - 服務器 此時客戶端知道服務器收到連接請求了
第三次:
客戶端 - - > 服務器 此時服務器知道客戶端收到了自己的回應

到這里,就可以認為客戶端與服務器已經建立了連接.

(圖2)

三次握手的詳細過程

開始,客戶端和服務器都處於 CLOSE 狀態.

此時,客戶端向服務器主動發出連接請求,服務器被動接受連接請求.

1、TCP服務器進程先創建傳輸控制塊TCB,時刻准備接受客戶端進程的連接請求,此時服務器就進入了 LISTEN(監聽)狀態

2、TCP客戶端進程也是先創建傳輸控制塊TCB,然后向服務器發出連接請求報文,此時報文首部中的同步標志位SYN=1,同時選擇一個初始序列號 seq = x,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號

3、TCP服務器收到請求報文后,如果同意連接,則發出確認報文。確認報文中的 ACK=1,SYN=1,確認序號是 x+1,同時也要為自己初始化一個序列號 seq = y,此時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據,但是同樣要消耗一個序號

4、TCP客戶端進程收到確認后還,要向服務器給出確認。確認報文的ACK=1,確認序號是 y+1,自己的序列號是 x+1.

5、此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。當服務器收到客戶端的確認后也進入ESTABLISHED狀態,此后雙方就可以開始通信了。

初始序列號 ISN 的取值

RFC793 中認為 ISN 要和一個假的時鍾綁定在一起ISN 每四微秒加一,當超過 2 的 32 次方之后又從 0 開始,要四個半小時左右發生 ISN 回繞

SYN 超時了怎么處理

在 Linux 中就是默認重試 5 次,並且就是階梯性的重試,間隔就是1s、2s、4s、8s、16s,再第五次發出之后還得等 32s 才能知道這次重試的結果,所以說總共等63s 才能斷開連接。

SYN Flood 攻擊

SYN Flood 攻擊: SYN 超時需要耗費服務端 63s 的時間斷開連接,也就說 63s 內服務端需要保持這個資源,所以不法分子就可以構造出大量的 client 向 server 發 SYN 但就是不回 server。 使得 server 的 SYN 隊列耗盡,無法處理正常的建連請求。

可以開啟 tcp_syncookies,那就用不到 SYN 隊列了。

SYN 隊列滿了之后 TCP 根據自己的 ip、端口、然后對方的 ip、端口,對方 SYN 的序號,時間戳等一波操作生成一個特殊的序號(即 cookie)發回去,如果對方是正常的 client 會把這個序號發回來,然后 server 根據這個序號建連。

或者調整 tcp_synack_retries 減少重試的次數,設置 tcp_max_syn_backlog 增加 SYN 隊列數,設置 tcp_abort_on_overflow SYN 隊列滿了直接拒絕連接。

四次揮手

(圖3)

四次揮手的詳細過程

數據傳輸完畢后,雙方都可以釋放連接.

此時客戶端和服務器都是處於ESTABLISHED狀態,然后客戶端主動斷開連接,服務器被動斷開連接.

1、客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等於前面已經傳送過來的數據的最后一個字節的序號加1),此時客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。

2、服務器收到連接釋放報文,發出確認報文,ACK=1,確認序號為 u+1,並且帶上自己的序列號seq=v,此時服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。

3、客戶端收到服務器的確認請求后,此時客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最終數據)

4、服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,確認序號為v+1,由於在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。

5、客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,確認序號為w+1,而自己的序列號是u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2∗MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。

6、服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。

為什么有等待 2*MSL的時間呢

MSL 是 Maximum Segment Lifetime,即報文最長生存時間,RFC 793 定義的 MSL 時間是 2 分鍾,Linux 實際實現是 30s,那么 2MSL 是一分鍾。

  • 就是怕被動關閉方沒有收到最后的 ACK,如果被動方由於網絡原因沒有到,那么它會再次發送 FIN, 此時如果主動關閉方已經 CLOSED 那就傻了,因此等一會兒。
  • 假設立馬斷開連接,但是又重用了這個連接,就是五元組完全一致,並且序號還在合適的范圍內,雖然概率很低但理論上也有可能,那么新的連接會被已關閉連接鏈路上的一些殘留數據干擾,因此給予一定的時間來處理一些殘留數據。

等待 2MSL 會產生什么問題?

如果服務器主動關閉大量的連接,那么會出現大量的資源占用,需要等到 2MSL 才會釋放資源。

如果是客戶端主動關閉大量的連接,那么在 2MSL 里面那些端口都是被占用的,端口只有 65535 個,如果端口耗盡了就無法發起送的連接了,不過我覺得這個概率很低,這么多端口你這是要建立多少個連接?

確認應答機制(ACK機制)

這里寫圖片描述

TCP將每個字節的數據都進行了編號,即為序列號.

每一個ACK都帶有對應的確認序列號,意思是告訴發送者,我已經收到了哪些數據; 下一次你要從哪里開始發.
比如,客戶端向服務器發送了1005字節的數據,服務器返回給客戶端的確認序號是1003,那么說明服務器只收到了1-1002的數據.1003,1004,1005都沒收到.
此時客戶端就會從1003開始重發.

超時重傳機制

這里寫圖片描述

這種情況下,主機B會收到很多重復數據.

那么TCP協議需要識別出哪些包是重復的,並且把重復的丟棄.

這時候利用前面提到的序列號,就可以很容易做到去重.

超時時間如何確定?

最理想的情況下,找到一個最小的時間,保證 “確認應答一定能在這個時間內返回”.

但是這個時間的長短,隨着網絡環境的不同,是有差異的.

如果超時時間設的太長,會影響整體的重傳效率; 如果超時時間設的太短,有可能會頻繁發送重復的包.

TCP為了保證任何環境下都能保持較高性能的通信,因此會動態計算這個最大超時時間.

這個最大超時時間就叫 RTT,即 Round Trip Time,然后根據這個時間制定超時重傳的時間 RTO,即 Retransmission Timeout。

Linux中(BSD Unix和Windows也是如此),超時以500ms為一個單位進行控制,每次判定超時重發的超時時間都是500ms的整數倍.

如果重發一次之后,仍然得不到應答,等待 2500ms 后再進行重傳. 如果仍然得不到應答,等待 4500ms 進行重傳.

依次類推,以指數形式遞增. 累計到一定的重傳次數,TCP認為網絡異常或者對端主機出現異常,強制關閉連接.

為什么還需要快速重傳機制?

超時重傳是按時間來驅動的,如果是網絡狀況真的不好的情況,超時重傳沒問題,但是如果網絡狀況好的時候,只是恰巧丟包了,那等這么長時間就沒必要。

於是又引入了數據驅動的重傳叫快速重傳,什么意思呢?就是發送方如果連續三次收到對方相同的確認號,那么馬上重傳數據。

因為連續收到三次相同 ACK 證明當前網絡狀況是 ok 的,那么確認是丟包了,於是立馬重發,沒必要等這么久。

image

看起來好像挺完美的,但是你有沒有想過我發送1、2、3、4這4個包,就 2 對方沒收到,1、3、4都收到了,然后不管是超時重傳還是快速重傳反正對方就回 ACK 2。

這時候要重傳 2、3、4 呢還是就 2 呢?

SACK 的引入是為了解決什么問題?

SACK 即 Selective Acknowledgment,它的引入就是為了解決發送方不知道該重傳哪些數據的問題。

我們來看一下下面的圖就知道了。

圖片

SACK 就是接收方會回傳它已經接受到的數據,這樣發送方就知道哪一些數據對方已經收到了,所以就可以選擇性的發送丟失的數據。

滑動窗口

剛才我們討論了確認應答機制,對每一個發送的數據段,都要給一個ACK確認應答. 收到ACK后再發送下一個數據段.

這樣做有一個比較大的缺點,就是性能較差. 尤其是數據往返時間較長的時候.

那么我們可不可以一次發送多個數據段呢?

這里寫圖片描述

一個概念: 窗口

窗口大小指的是無需等待確認應答就可以繼續發送數據的最大值.

上圖的窗口大小就是4000個字節 (四個段).

發送前四個段的時候,不需要等待任何ACK,直接發送

收到第一個ACK確認應答后,窗口向后移動,繼續發送第五六七八段的數據…

因為這個窗口不斷向后滑動,所以叫做滑動窗口.

操作系統內核為了維護這個滑動窗口,需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答

只有ACK確認應答過的數據,才能從緩沖區刪掉.

這里寫圖片描述

如果出現了丟包,那么該如何進行重傳呢?

1、數據包已經收到,但確認應答ACK丟了.

這種情況下,部分ACK丟失並無大礙,因為還可以通過后續的ACK來確認對方已經收到了哪些數據包.

2、數據包丟失

可以通過高速重發控制控制

流量控制

接收端處理數據的速度是有限的. 如果發送端發的太快,導致接收端的緩沖區被填滿,這個時候如果發送端繼續發送,就會造成丟包,進而引起丟包重傳等一系列連鎖反應.

因此TCP支持根據接收端的處理能力,來決定發送端的發送速度.

這個機制就叫做 流量控制(Flow Control)

接收端將自己可以接收的緩沖區大小放入 TCP 首部中的 “窗口大小” 字段,

通過ACK通知發送端;

窗口大小越大,說明網絡的吞吐量越高;

接收端一旦發現自己的緩沖區快滿了,就會將窗口大小設置成一個更小的值通知給發送端;

發送端接受到這個窗口大小的通知之后,就會減慢自己的發送速度;

如果接收端緩沖區滿了,就會將窗口置為0;

這時發送方不再發送數據,但是需要定期發送一個窗口探測數據段,讓接收端把窗口大小再告訴發送端.

這里寫圖片描述

擁塞控制

雖然TCP有了滑動窗口這個大殺器,能夠高效可靠地發送大量數據.

但是如果在剛開始就發送大量的數據,仍然可能引發一些問題.

因為網絡上有很多計算機,可能當前的網絡狀態已經比較擁堵.

在不清楚當前網絡狀態的情況下,貿然發送大量數據,很有可能雪上加霜.

因此,TCP引入 慢啟動 機制,先發少量的數據,探探路,摸清當前的網絡擁堵狀態以后,再決定按照多大的速度傳輸數據.

這里寫圖片描述

在此引入一個概念 擁塞窗口

發送開始的時候,定義擁塞窗口大小為1;

每次收到一個ACK應答,擁塞窗口加1;

每次發送數據包的時候,將擁塞窗口和接收端主機反饋的窗口大小做比較,取較小的值作為實際發送的窗口
像上面這樣的擁塞窗口增長速度,是指數級別的.

“慢啟動” 只是指初使時慢,但是增長速度非常快.

為了不增長得那么快,此處引入一個名詞叫做慢啟動的閾值,當擁塞窗口的大小超過這個閾值的時候,不再按照指數方式增長,而是按照線性方式增長.

image

  • 當TCP開始啟動的時候,慢啟動閾值等於窗口最大值
  • 在每次超時重發的時候,慢啟動閾值會變成原來的一半,同時擁塞窗口置回1

少量的丟包,我們僅僅是觸發超時重傳;

大量的丟包,我們就認為是網絡擁塞;

當TCP通信開始后,網絡吞吐量會逐漸上升;

隨着網絡發生擁堵,吞吐量會立刻下降.

擁塞控制,歸根結底是TCP協議想盡可能快的把數據傳輸給對方,但是又要避免給網絡造成太大壓力的折中方案.

延遲應答

如果接收數據的主機立刻返回ACK應答,這時候返回的窗口可能比較小.

假設接收端緩沖區為1M. 一次收到了500K的數據;

如果立刻應答,返回的窗口大小就是500K;

但實際上可能處理端處理的速度很快,10ms之內就把500K數據從緩沖區消費掉了; 在這種情況下,接收端處理還遠沒有達到自己的極限,即使窗口再放大一些,也能處理過來;

如果接收端稍微等一會兒再應答,比如等待200ms再應答,那么這個時候返回的窗口大小就是1M

窗口越大,網絡吞吐量就越大,傳輸效率就越高.
TCP的目標是在保證網絡不擁堵的情況下盡量提高傳輸效率;

那么所有的數據包都可以延遲應答么?

肯定也不是

有兩個限制

數量限制: 每隔N個包就應答一次

時間限制: 超過最大延遲時間就應答一次

具體的數量N和最大延遲時間,依操作系統不同也有差異

一般 N 取2,最大延遲時間取200ms

粘包問題

首先要明確,粘包問題中的 “包”,是指應用層的數據包.

在TCP的協議頭中,沒有如同UDP一樣的 “報文長度” 字段

但是有一個序號字段.

站在傳輸層的角度,TCP是一個一個報文傳過來的. 按照序號排好序放在緩沖區中.

站在應用層的角度,看到的只是一串連續的字節數據.

那么應用程序看到了這一連串的字節數據,就不知道從哪個部分開始到哪個部分是一個完整的應用層數據包.

此時數據之間就沒有了邊界,就產生了粘包問題

那么如何避免粘包問題呢?

歸根結底就是一句話,明確兩個包之間的邊界

對於定長的包

  • 保證每次都按固定大小讀取即可
    例如上面的Request結構,是固定大小的,那么就從緩沖區從頭開始按sizeof(Request)依次讀取即可

對於變長的包

  • 可以在數據包的頭部,約定一個數據包總長度的字段,從而就知道了包的結束位置
    還可以在包和包之間使用明確的分隔符來作為邊界(應用層協議,是程序員自己來定的,只要保證分隔符不和正文沖突即可)

對於UDP協議來說,是否也存在 “粘包問題” 呢?

對於UDP,如果還沒有向上層交付數據,UDP的報文長度仍然存在.

同時,UDP是一個一個把數據交付給應用層的,就有很明確的數據邊界.

站在應用層的角度,使用UDP的時候,要么收到完整的UDP報文,要么不收.不會出現收到 “半個” 的情況.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM