TCP常見問題總結

qq_40281445發表於2020-10-12

TCP常見問題

TCP全稱Transmission Control Protocol,即傳輸控制協議。TCP控制的內容主要包括:

  • 可靠性
  • 有序性
  • 流量控制
  • 擁塞控制

為何不在IP層對資料進行上述控制?

不在IP層實現控制是因為IP層涉及到的裝置很多,裝置之間靠IP來定址,如果在IP層實現控制,那麼涉及到的裝置都要關心很多事情,整體的傳輸效率會收到影響。

TCP所謂的連線只是雙方都維護了一個狀態

TCP協議頭

在這裡插入圖片描述

如圖:

  • TCP包只有埠,沒有IP
  • Seq就是Sequence Number即序號,用來解決亂序問題
  • ACK就是Acknowledgement Number,即確認號,用來解決丟包的情況,告知傳送方接收到的包的序號
  • 標誌位就是TCP flags,用來標記包的型別,用來控制TCP的狀態
  • 視窗就是滑動視窗Sliding Window,用來進行流量控制

三次握手

在這裡插入圖片描述

目的有二:

  • 確認雙方的傳送接收功能都正常
  • 初始化Seq Number,SYN的全稱為Synchronize Sequence Numbers,這個序號是用來保證傳輸資料的正確性

初始序號ISN的取值

如果ISN從0開始,假設建立好連線之後傳送了第20個包之後網路斷了,client重啟了,序號又從0開始,此時服務端返回第20個包的ack客戶端就沒法處理了。

所以RFC739中認為ISN要和一個假設的時鐘繫結在一起:

ISN每四微秒加一,當超過2的32次方之後又從0開始,要四個半小時左右發生ISN迴繞。

所以ISN變成一個遞增值,真實的實現還需要加一些隨機值在裡面,防止被不法分子猜到ISN。

SYN超時如何處理

慢慢重試,階梯性重試。

Linux中就是預設重試5次,並且間隔是1s、2s、4s、8s、16s,在第五次發出後還得再等32s才能知道這次重試的結果,總共等63秒才能斷開連線。

SYN Flood攻擊

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佇列滿了直接拒絕連線。

為什麼要四次揮手?

在這裡插入圖片描述
因為TCP是全雙工協議,也就是說雙方都要關閉,每一方都想對方傳送FIN和回應ACK,所以看起來就是四次。

主動關閉方的狀態是FIN_WAIT_1到FIN_WAIT_2,然後再到TIME_WAIT狀態。

被動關閉方是CLOSE_WAIT到LAST_ACK狀態。

為什麼要有TIME_WAIT?

主動斷開方在接收到被動關閉方的FIN並回復ACK之後並沒有直接進入CLOSED狀態,而是等待了2MSL。

MSL是Maximum Segment Lifetime,即報文最長生存時間,RFC793定義的MSL時間是2分鐘,Linux實際實現是30s,那麼2MSL是1分鐘。

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

如果伺服器主動關閉大量連線,那麼會出現大量的資源佔用,需要等到2MSL才會釋放資源。

如何解決2MSL產生的問題?

快速回收,即不等2MSL就回收,Linux的引數是tcp_tw_recycle,還有tcp_timestamps不過是預設開啟的。

重用,即開啟tcp_tw_reuse當然也是需要tcp_timestamps的。tcp_tw_reuse是用在連線發起方的,而我們的服務端基本上是被動接收連線方。

tcp_tw_reuse是發起新連線的時候可以複用超過1s的處於TIME_WAIT狀態的連線,所以對服務端壓力減小的效果甚微。它重用的是發起方處於TIME_WAIT的連線。

這裡還有一個SO_REUSEADDR,這是一個使用者態選項,而tcp_tw_reuse是一個核心選項。然後SO_REUSEADDR主要用在啟動服務的時候,如果此時埠被佔用了並且這個連線處於TIME_WAIT狀態,那麼可以重用這個埠,如果不是TIME_WAIT狀態,丟擲Address already in use。

所以對於服務端,tcp_tw_recycle和tcp_tw_reuse都不太好用。

所以服務端儘量不要主動關閉連線,把關閉連線的請求放到服務端來做,減少服務端出現TIME_WAIT狀態的連線出現的機率,提高服務端的資源利用率。

超時重傳機制是為了解決什麼問題?

解決了丟包問題。

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

超時重傳是靠時間來驅動的,如果網路狀況不好,超時重傳沒問題,如果網路狀況好的時候,只是恰巧丟包了,那等這麼長時間就沒必要。所以引入了快速重傳,如果傳送方連續三次收到對方相同的確認號,就可以證明網路狀況沒問題,確認是丟包了,那麼馬上重傳資料。

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

如果傳送方傳送了1、2、3、4這四個包,就2對方沒收到,然後不管是超時重傳還是快速重傳反正對方就回ACK 2,這時候是要重傳2、3、4呢,還是隻傳2?

SACK的引入就是為了解決傳送方不直到該重傳哪些資料的問題。

在這裡插入圖片描述
如圖所示,SACK就是接收方會回傳它已經接收到的資料,這樣傳送方就知道哪一些資料對方已經收到了。

D-SACK又是啥?

D-SACK是SACK的擴充套件,它利用SACK的第一段來描述重複接收的不連續的資料序號,如果第一段描述的範圍被ACK覆蓋了,說明重複了,比如我都ACK到6000了你還給我回SACK 5000-5500呢?

引數是tcp_dsack,Linux2.4之後預設開啟。

那直到重複了有什麼用呢?

  • 知道重複了說明對方收到剛才那個包了,所以是回來了ACK包丟了。
  • 是不是包亂序的,先發的包後到?
  • 是不是自己太著急了,RTO太小了?
  • 是不是被資料複製了,搶先一步呢?

滑動視窗的作用?

滑動視窗用來做流量控制,接收方告訴傳送方他還能接收多少資料,然後傳送方就可以根據這個資訊來控制資料的傳送。

如果接收方回覆的視窗一直是0怎麼辦?

TCP有一個Zero Window Probe技術,傳送方得知視窗是0之後,回去探測這個接收方到底行不行,也就是傳送ZWP包給接收方,如果多次之後都不行可以直接RST,傳送次數根據具體實現而定。

已經有滑動視窗了為什麼還要有擁塞控制?

加擁塞控制是因為TCP不僅僅就管兩端之間的情況,還需要知曉整體的網路情形。因為如果網路狀況很差,所有的連線都無腦重傳的話,網路將變的更差,更加擁堵。所以需要擁塞控制來避免這種情況的發生。

如何實現擁塞控制?

主要有以下幾個步驟:

  • 慢啟動
  • 擁塞避免,感覺差不多了減速看看
  • 擁塞發生後,快送重傳/回覆
    在這裡插入圖片描述
    慢啟動,一開始初始化cwnd(congestion window)為1,然後每收到一個ACK就cwnd++,並且沒過一個RTT就cwnd = 2*cwnd。

然後到了一個閾值,也就是ssthresh(slow start threshold)的時候就進入擁塞避免階段。這個階段就是每收到一個ACK就cwnd = cwnd+1/cwnd並且每一個RTT就cwnd++。

然後一直線性增加,直到開始丟包的時候,一種是超時重傳,一種是快速重傳。

如果發生超時重傳,直接將ssthresh置為當前cwnd的一半,然後cwnd直接變為1,進入慢啟動階段。

如果是快速重傳,那麼有兩種實現,一種是TCP Tahoe,和超時重傳一樣的處理。一種是TCP Reno,這個實現是把cwnd=cwnd/2,然後把ssthresh設定為當前的cwnd。

然後進入快速恢復階段,將cwnd = cwnd + 3(因為快速重傳有三次),重傳DACK指定的包,如果再收到一個DACK則cwnd++,如果收到的是正常的ACK那麼就將cwnd設為ssthresh大小,進入擁塞避免階段。

總結

TCP是面向連線的,提供可靠、有序的傳輸並且還提供流控和擁塞控制,單獨提取出TCP層而不是在IP層實現是因為IP層有更多的裝置需要使用,加了複雜的邏輯不划算。

三次握手主要是為了定義初始序列號為了之後的傳輸打下基礎,四次揮手是因為TCP是全雙工協議,需要雙方都斷開連線。

SYN超時了就階梯性重試,如果有SYN攻擊,可以加大半連線佇列,或減少重試次數,或直接拒絕。

還提供流控和擁塞控制,單獨提取出TCP層而不是在IP層實現是因為IP層有更多的裝置需要使用,加了複雜的邏輯不划算。

三次握手主要是為了定義初始序列號為了之後的傳輸打下基礎,四次揮手是因為TCP是全雙工協議,需要雙方都斷開連線。

SYN超時了就階梯性重試,如果有SYN攻擊,可以加大半連線佇列,或減少重試次數,或直接拒絕。

TIME_WAIT是怕對方沒收到最後一個ACK,然後又發了FIN過來,並且也是等待處理網路上殘留的資料,怕影響新連線。

相關文章