TCP詳解(報文結構,三次握手,四次揮手,tcp各種機制)
linuxtcp圖解
- tcp頭部(20-60位元組)
- TCP埠號
TCP的連線是需要四個要素確定唯一一個連線:
(源IP,源埠號)+ (目地IP,目的埠號)
所以TCP首部預留了兩個16位作為埠號的儲存,而IP地址由上一層IP協議負責傳遞
源埠號和目地埠各佔16位兩個位元組,也就是埠的範圍是2^16=65535
另外1024以下是系統保留的,從1024-65535是使用者使用的埠範圍 - TCP的序號和確認號:
32位序號 seq:Sequence number 縮寫seq ,TCP通訊過程中某一個傳輸方向上的位元組流的每個位元組的序號,通過這個來確認傳送的資料有序,比如現在序列號為1000,傳送了1000,下一個序列號就是2000。
32位確認號 ack - TCP的標誌位
每個TCP段都有一個目的,這是藉助於TCP標誌位選項來確定的,允許傳送方或接收方指定哪些標誌應該被使用,以便段被另一端正確處理。
用的最廣泛的標誌是 SYN,ACK 和 FIN,用於建立連線,確認成功的段傳輸,最後終止連線。- SYN:簡寫為
S
,同步標誌位,用於建立會話連線,同步序列號; - ACK: 簡寫為
.
,確認標誌位,對已接收的資料包進行確認; - FIN: 簡寫為
F
,完成標誌位,表示我已經沒有資料要傳送了,即將關閉連線; - PSH:簡寫為
P
- RST:簡寫為
R
,重置標誌位,用於連線復位、拒絕錯誤和非法的資料包; - URG:簡寫為
U
,緊急標誌位,表示資料包的緊急指標域有效,用來保證連線不被阻斷,並督促中間裝置儘快處理;
- SYN:簡寫為
TCP三次握手
-
客戶端向伺服器發來請求,SYN為1(表示建立連線),seq隨機生成一個序列號J,客戶端進入SYN_SENT狀態。
-
伺服器在收到請求時,發出應答包,SYN=1,ACK=1(表示已接受到資料包),返回確認號ack=J+1,生成一個序列號seq=K
-
客戶端收到資料包,判斷ack=J+1,ACK=1是否正確,正確返回ACK為1,ack為K+1的資料包,進入
ESTABLISHED(已連線)。伺服器判斷髮來資料包是否正確,然後也進入ESTABLISHED狀態
TCP四次揮手
兩端都可以傳送斷開請求,以客戶端為例
- 客戶端傳送一個FIN=M(finish請求斷開),進入FIN_WAIT1狀態
- 伺服器收到斷開請求,發一個ack=M+1,等待自己確認服務端傳送完資料,進入CLOSE_WAIT,客戶端收到進入FIN_WAIT2
- 服務端傳送完資料,發一個FIN=N,進入LAST_WAIT
- 客戶端收到之後,進入TIME_WAIT狀態,發一個ack = N+1,ACK = 1.服務端接收到斷開連線,客戶端等待2MSL後沒有收到資料,就斷開連線,(MSL是資料包在網路中失效的最大時間單位)
為什麼要等待2MSL?
有以下兩個原因:
- 第一點:保證TCP協議的全雙工連線能夠可靠關閉:
由於IP協議的不可靠性或者是其它網路原因,導致了Server端沒有收到Client端的ACK報文,那麼Server端就會在超時之後重新發送FIN,如果此時Client端的連線已經關閉處於CLOESD
狀態,那麼重發的FIN就找不到對應的連線了,從而導致連線錯亂,所以,Client端傳送完最後的ACK不能直接進入CLOSED
狀態,而要保持TIME_WAIT
,當再次收到FIN的收,能夠保證對方收到ACK,最後正確關閉連線。 - 第二點:保證這次連線的重複資料段從網路中消失
如果Client端傳送最後的ACK直接進入CLOSED
狀態,然後又再向Server端發起一個新連線,這時不能保證新連線的與剛關閉的連線的埠號是不同的,也就是新連線和老連線的埠號可能一樣了,那麼就可能出現問題:如果前一次的連線某些資料滯留在網路中,這些延遲資料在建立新連線後到達Client端,由於新老連線的埠號和IP都一樣,TCP協議就認為延遲資料是屬於新連線的,新連線就會接收到髒資料,這樣就會導致資料包混亂。所以TCP連線需要在TIME_WAIT狀態等待2倍MSL,才能保證本次連線的所有資料在網路中消失。
確認應答機制
根據前面的知識,就是ack+1 的應答,當發資料端發1000資料,接收端就會告訴對方我需要發1001資料,下一次就從1001繼續傳送。
滑動視窗
如果是一發一收的性質的話,效率太差,tcp提供滑動視窗就是實現快速傳送。
就是開闢了緩衝區,來確認那些資料沒有傳送,哪些資料可以從緩衝區刪除了。
如果在這種情況中出現了丟包現象,應該如何重發呢?
資料到達接收方,但是應答報文丟失:可以更具後邊的ACK確認。假設傳送方傳送1-1000的資料,接收方收到返回確認ACK,但是返回的ACK丟失了,另一邊傳送1001-2000收到的確認ACK 2001,就可以認為1-1000資料接收成功 。
資料包之間丟失: 當某一段報文段丟失之後,傳送端會一直收到 1001 這樣的ACK,就像是在提醒傳送端 "我想要的是 1001" 一樣,如果傳送端主機連續三次收到了同樣一個"1001" 這樣的應答,就會將對應的資料 1001 - 2000 重新發送,這個時候接收端收到了 1001 之後, 再次返回的ACK就是7001了。因為2001 - 7000接收端其實之前就已經收到了,被放到了接收端作業系統核心的接收緩衝區中。這種機制被稱為 “高速重發控制”(也叫 "快重傳")。
快恢復(與快重傳配合使用)
採用快恢復演算法時,慢開始只在TCP連線建立時和網路出現超時時才使用。
當傳送方連續收到三個重複確認時,就執行“乘法減小”演算法,把ssthresh門限減半。但是接下去並不執行慢開始演算法。
考慮到如果網路出現擁塞的話就不會收到好幾個重複的確認,所以傳送方現在認為網路可能沒有出現擁塞。所以此時不執行慢開始演算法,而是將cwnd設定為ssthresh的大小,然後執行擁塞避免演算法。
超時重傳機制
傳送端沒有收到應答的資料,就會重新發送,有兩種情況
1、接收端就沒有收到資料,這時候傳送端看時間到了,就會重新發送資料給接收端
2、當接收端收到資料,在應答包傳送的時候出現了丟包,這個時候傳送端沒有收到應答包還是會重新發送,但是接受端已經有資料了,這是就根據序列號來識別是否是重複的資料。
如何界定這個特定的時間重傳
最理想的情況下,找到一個最小的時間保證確認應答一定能在這時間內返回
但是這個時間的長短,隨著網路環境的不同,也是有差異的。
如果超時時間設得太長,會影響整體的重傳效率
如果超時時間設的太短,有可能平凡的傳送衝符的資料包。
TCP為了抱枕個無論在何種環境下都能比較高效能的通訊,因此會動態計算這個最大超時時間:
Linux中,超時以500ms為一個單位進行控制,每次判定超時重發的超時時間都是500ms的整數倍。
如果重發一次,任然得不到應答,就等待2500ms後在進行重傳
如果還得不到應答,等待4500ms再重傳,一次類推,以指數形式遞增。
累積到一定的重傳次數,TCP認為網路或者對端主機出現異常,就會強制關閉連線。
流量控制
當傳送端傳送速度過於快速,接收端緩衝區滿的時候,這是在傳送資料就會出現丟包的現象,tcp會自動根據接收端的視窗大小,進行資料的大小的調整,這時候就要用到tcp報頭中的視窗的16位元組的大小了,視窗數字越大,就證明接收端資料吞吐量越大。如果接收緩衝區滿了,就會將視窗置為0,這時傳送方不再發送資料。但是需要定期的傳送一個試探視窗,目的是為了取探測資料段,是接收端把視窗大小告訴傳送端。
擁塞控制
如果網路中的網路狀態不好,這是伺服器傳送大量的資料就會使網路跟家的阻塞,這是tcp就引入了慢啟動機制。先發少量的資料,後面在逐漸增加。
這種增加資料的吞吐量是一直指數形式增加的,但是不會這樣一直增加,tcp有個叫慢啟動閾值,當到這個數值時候,增長的就會以線性增長
- 當
TCP
開始啟動的時候,慢啟動閾值
等於視窗最大值 - 在每次
超時重發
的時候,慢啟動閾值
會變成原來的一半同時擁塞視窗置回1
延遲應答
就是加入這次緩衝區收到500k資料,立即返回應答包,返回的視窗大小也是500,但是如果等他個十幾毫秒,就會收到1M資料,就會返回視窗大小更大,服務端就會發送很多資料過來,吞吐量就很大。效率更高。
- 數量限制: 每隔
N個包
就應答一次 - 時間限制: 超過大
延遲時間
就應答一次
注:具體的數量和超時時間, 依作業系統不同也有差異; 一般N取2
, 超時時間取200ms
捎帶應答
就是在延遲應答基礎上,看下你的網路狀況是不是很好,接收端就會在ACK中捎帶應答,自己的網路狀況。
如何避免粘包問題呢?明確兩個包之間的邊界
對於定長的包,保證每次都按固定大小讀取即可。例如一個Request結構, 是固定大小的, 那麼就從緩衝區從頭開始按sizeof(Request)依次讀取即可
對於變長的包,可以在包頭的位置,約定一個包總長度的欄位,從而就知道了包的結束位置。
對於變長的包,還可以在包和包之間使用明確的分隔符(應用層協議是程式設計師自己來定義的, 只要保證分隔符不和正文衝突即可)。
對於UDP協議,如果還沒有上層交付資料, UDP的報文長度仍然在。 同時UDP是一個一個把資料交付給應用層,這樣就有存在明確的資料邊界,站在應用層的角度, 使用UDP的時候要麼收到完整的UDP報文要麼不收,不會出現"半個"的情況。
TCP連線異常情況:
程序終止:程序終止會釋放檔案描述符,仍然可以傳送FIN,和正常關閉沒有什麼區別。機器重啟和程序終止一樣。
機器掉電/網線斷開:接收端認為連線還在,一旦接收端有寫入操作,接收端發現連線已經不在了,就會進行reset。即使沒有寫入操作,TCP自己也內建了一個保活定時器,會定期詢問對方是否還在。如果對方不在,也會把連線釋放。應用層的某些協議, 也有一些這樣的檢測機制.例如HTTP長連線中, 也會定期檢測對方的狀態.Q在QQ 斷線之後, 也會定期嘗試重新連線。
參考:
https://blog.csdn.net/hansionz/article/details/86435127
https://blog.csdn.net/qq_40927789/article/details/80607610