通過學習視頻加博客的組合形式,整理了一些關于TCP協議的知識。
*圖源:@臨界~的csdn博客。
一、TCP建立連接
TCP的建立連接,大致可以分為面向連接、TCP報文結構、TCP的三次握手、TCP的建立狀態、SYN泛洪攻擊。
1.1、面向連接
面向連接 --- 發送數據的時候需要先建立一條點到點的連接,TCP采用這種連接方式,UDP被認為是一種“無連接”的協議。
點到點的連接說明,在TCP的通訊中,只有通訊雙方,而不存在第三方。而UDP可以一個主機同時向多個主機發送消息。
這里注意:連接其實是一個參數比對的過程
源IP地址、目標IP地址、源端口、目標端口 --- ”TCP的四元組“,TCP四元組可以唯一的區分和標識一條TCP的連接。
1.2、TCP的報文結構
TCP作為傳輸層協議,源端口和目標端口相當于它的一個地址,這是兩端口存在的意義,因為傳輸層需要實現端到端的傳輸。
源端口號、目標端口號 --- 端口號主要是傳輸層定義的一種地址,其目的是區分上層應用。
序號(序列號) ---?TCP是一個基于“字節流”的協議,這個序列號就是字節流的編號,每一個字節都會有自己的序列號。
比方說現在有兩個主機A和B,他們通過TCP進行連接通信,假設現在A想要發送1000個字節的數據,而由于MTU(最大傳輸單元)的限制,數據會進行一個分段,然后根據rwnd(接收窗口)一組一組的進行發送,而TCP的確認機制是累積確認,但是怎么累積確認呢,通過發送的ACK包上的Ack Num來進行確認。所以說,當A第一次發送的是0~999序號的字節流后,B發送的確認序列號是1000,即表明了B想要的下一次數據的序列號,同時還告訴A自己之前收到了0~999的序號的字節流。
TCP的確認序列號 --- 接收方期望收到發送方發送的下一個字節的序號 --- 累積確認
首部長度 --- TCP報文頭部的長度 --- TCP報文存在選項字段,稱之為可變長頭部,TCP頭部最短20字節(通常,選項字段為空)
保留 --- 用 000000 進行填充
標記位 --- 保留位后面的六組字母,每組占一位,當為1時表示激活,為0時未激活
? ? ? ? URG --- 緊急標記位 --- TCP的數據報文中有緊急處理的數據(應用層上層來定義),把需要緊急處理的數據放到數據報文中的最前面,當URG置1時,緊急指針被激活,緊急指針位于需要緊急處理數據長度的位置
? ? ? ? ACK --- 確認標記位 ---?當ACK置1時,確認序列號被激活
主機A給主機B發送數據段的時候,主機B會開啟一個緩存空間用來存儲數據段
? ? ? ? PSH --- 當該位置1的時候,把當前緩存空間的數據段推送到進程中(在滑動窗口中的可以知道所以然),可能當前的數據段需要優先處理等需求
? ? ? ? RST --- 當該位置1,強制斷開TCP的連接
? ? ? ? SYN ---? 希望建立TCP連接的時候,該位置1,同時會隨機給序列號設置一個初始值
? ? ? ? FIN --- 當該位置1,表示正常TCP協商斷開
窗口大小 --- 實現流量控制的一個重要參數
流量控制也是保障TCP傳輸可靠的一個舉措
校驗和 --- 數據完整性檢驗,TCP會有偽頭部校驗?
? ? ? ? 偽頭部校驗,TCP校驗的時候會帶上IP頭部進行校驗,當校驗完成后發給IP協議進行完整檢驗
? ? ? ? ? ? ? ? IP頭部(12字節):32位源IP、32位目標IP、8位保留、8位協議、16位報文長度
這里數據包只是到了TCP,明明還沒到網絡層進行封裝,怎么會有IP頭部呢?
? ? ? ? 這里用到了預封裝,而預封裝的IP頭部就稱為偽頭部
字節流的序號應該是連續的,TCP的會話建立是全雙工模式。
第二次的確認包中不止可以帶有確認序列號,還可以攜帶數據,因為此時客戶端對于服務器的會話已經建立。
1.3、TCP的三次握手?
TCP的連接過程是由參與TCP通訊的雙方中的其中一方發起的,我們一般認定發送方為客戶端,而另一方為服務器。同時TCP的會話是一個雙向的會話。
三次握手的第一個報文:
SYN報文:其是沒有攜帶數據的,做請求連接這件事情,這個時候接收方會給TCP分配一部分的緩存空間,定義一些初始變量。
三次握手的第二個報文:
Ack報文:由于這個時候沒有數據,所以是初始化的seq+1,這個確認包除了能表示我收到了你的請求SYN包,還能表示我同意和你進行一個會話的建立,此時發送方對于接收方的會話已經建立(發送方對接收方的TCP已經建立)。
SYN報文(與第一個SYN無關):表示接收方也想和發送方進行一個TCP的建立,這時發送方也應該分配一部分緩存空間給TCP,定義一些初始變量。
三次握手的第三個報文:
ACK報文: 此時發送方對于接收方的TCP會話已經建立是可以發送數據的,所以發送的數據序列號seq應該是初始化的seq+1(即client_isn + 1)
問:為什么每次的SYN報文后給定的seq是一個隨機值,而不是一個固定值?
? ? ? ? 這樣的設計可以防止歷史報文,如果seq是一個固定值,當兩個報文的四元組相同時,但是發送的時間不同,由于鏈路堵塞等問題導致第一次的TCP報文還殘留在鏈路中,當第二次TCP報文發送到對端的時候兩個報文都來到對端,對端無法判斷哪一個是歷史報文,哪一個是新的TCP報文。
隨機序列號的作用:
隔離序列號空間:新連接的序列號范圍與舊連接的序列號范圍完全無關,舊報文中的序列號幾乎不可能落在新連接的接收窗口內。
窗口校驗:TCP接收端會檢查報文的序列號是否在當前的接收窗口內。即使舊報文到達,其序列號大概率不在新窗口的有效范圍內,會被直接丟棄。
1.4、TCP的狀態變化
客戶端:
? ? ? ? 1、關閉狀態 --- 在發送SYN報文請求建立連接后,進入到下一個狀態。
? ? ? ? ?2、SYN_SENT --- 客戶端等待服務器返回SYN_ACK報文。
? ? ? ? 3、建立完成 --- 收到服務器返回的SYN_ACK報文,因為此時客戶端向服務器的會話已經建立完成,所以,客戶端發送給服務器的最后一個ACK報文,是允許攜帶數據的。
服務器:
? ? ? ? 1、關閉狀態,當服務器的應用程序創建一個監聽的套接字后,將進入到下一個狀態。
? ? ? ? 2、Listen(偵聽狀態):當接收到客戶端發送的SYN報文后,為TCP連接分配緩存空間,同時,發送SYN_ACK報文段,然后進入到下一個狀態。
? ? ? ? 3、SYN_RCVD,等待客戶端回復ACK,收到之后,將進入到下一個狀態,超時時間是1min。
????????? 4、建立完成 --- TCP雙向會話均建立完成。
TCP異常連接:
? ? ? ? 當客戶端發送TCP連接報文的時候,服務器沒有進入到Listen狀態,無法處理發過來的TCP報文,所以當接收到客戶端的建立報文后,會回復一個RST(重置)的TCP報文,表示要中斷本次連接。
1.5、TCP的SYN泛洪攻擊
Dos攻擊 --- 拒絕服務,發送大量數據包,導致網絡擁塞無法正常處理業務,一般泛洪攻擊都是Dos攻擊
SYN泛洪攻擊(本質上是Dos攻擊):發送SYN報文后,服務器會分配一個緩存空間然后發送SYN_ACK報文,但是此時攻擊者不發送第三次握手的ACK報文,導致緩存空間的不斷占用。
應對方法:
? ? ? ? 1、防火墻代理 --- 采用防火墻充當中間人,由原來的三次握手變成六次握手,但是防火墻被攻陷也相當于服務器被攻陷,所以防火墻也有自我保護機制,每目標IP代理閾值(直接放通,直到達到閾值(根據業務流量決定))和每目標IP的丟棄閾值(訪問數量太大,達到了丟棄閾值,則直接丟棄),丟棄閾值會比代理閾值大。
? ? ? ? 2、SYN Cookie --- 源IP、目標IP、源端口號、目標端口(四元組)再加上一個隨機數通過hash算法得到一個摘要值,這個摘要值就是SYN Cookie。 當客戶端發送SYN報文請求連接后,服務器不分配緩存空間,而是計算出SYN Cookie,當作服務器SYN報文中的Seq Num,然后等待客戶端發送的ACK報文,再通過hash算法計算出客戶端發送ACK報文的摘要值,進行加1匹配,成功后才分配緩存空間。
1.6、TCP三次的原因
問:TCP建立連接為什么必須是三次握手,而不是兩次或四次?
為什么不是四次?
? ? ? ? TCP會話的建立是雙向的,SYN_ACK報文包可以一起進行一個發送就沒必要分開,這樣就浪費了資源。
為什么不能是兩次?
? ? ? ? 兩次的過程只能是在客戶端發送SYN報文后,服務器就分配緩存空間,同時進入到ES狀態,然后發送SYN_ACK報文,客戶端接收后同樣分配緩存空間并進入到ES狀態。
三次握手可以防止舊重復連接造成混亂。
????????客戶端發送了一個SYN報文請求建立連接后突然down了,但是發送的SYN報文由于網絡堵塞在鏈路中停留,后面客戶端恢復連接,發送了一個新的SYN報文,但是舊的SYN報文比新的SYN報文先到,然后服務器發送SYN_ACK報文,但是此時的ACK不是客戶端想要的值,則客戶端就會發送一個RST報文中斷連接,一段時間后,新的SYN報文到達然后按照正常流程建立TCP雙向連接。
? ? ? ? 當只有兩次握手就可以建立TCP連接時,客戶端發送了一個SYN報文請求建立連接后突然down了,但是發送的SYN報文由于網絡堵塞在鏈路中停留,后面客戶端恢復連接,發送了一個新的SYN報文,但是舊的SYN報文比新的SYN報文先到,此時由于是兩次握手,導致服務器會直接建立連接,然后在SYN_ACK報文中攜帶數據,后面流程跟三次握手差不多。只是在發送SYN_ACK報文這一過程中發送了不必要的數據,浪費了資源。
二、TCP斷開連接
TCP的”四次揮手“,由于在發送FIN報文的時候會攜帶數據,所以就不會像連接一樣需要序列號,而且TCP的斷開是誰先發送完數據誰發送斷開請求,不像建立連接一樣是客戶端先向服務器發送SYN請求報文。
2.1、TCP的“四次揮手”
FIN報文是可以攜帶數據的,一般是攜帶數據的最后一小段字節流。
誰先發送斷開請求,誰就是下面的狀態流程:
建立完成狀態 --- 發送完所以字節流后,攜帶最后一組字節流的數據段同時將FIN標記位置1,之后進入到下一個狀態。
1、FIN_WAIT_1 --- 等待服務器回復ACK,收到ACK應答后,將進入到下一個狀態。
2、FIN_WAIT_2 --- 等待服務器發送FIN斷開請求,將回復ACK進行確認,進入下一個狀態。
3、TIME_WAIT --- 等待2MSL時間后進行下一個狀態。
4、CLOSE --- 關閉狀態,斷開TCP連接,釋放掉所有TCP連接占用的資源。
后面發送斷開請求的狀態流程:
1、建立完成狀態 --- 收到客戶端發送的FIN斷開請求后,服務器將回復一個ACK確認報文,之后,進入到下一個狀態。
2、CLOSED_WAIT --- 等待服務器自身字節流的發送,當自身所有字節流傳遞完畢后,將發送一個FIN斷開請求,之后進入到下一個狀態。
3、LAST_ACK ---? ?等待客戶端進行最后的ACK應答,當收到客戶端發送的ACK確認報文之后,進入到下一個狀態。
4、CLOSE --- 關閉狀態,斷開TCP連接,釋放掉所有TCP連接占用的資源。
2.2、WIME_WAIT存在的原因
問:為什么要有TIME_WAIT狀態?
? ? ? ? 保證TCP會話可以正常關閉,在正常的TCP斷開連接的流程中,如果TIME_WAIT時間過短或沒有,會導致在客戶端發送給服務器ACK確認報文丟失的之后,服務器重傳的FIN報文,客戶端無法識別,然后發送RST,導致這次會話異常斷開;如果TIME_WAIT的時間合適,就可以接收到重傳的FIN報文就可以正常斷開連接。
TIME_WAIT時間過短或沒有導致異常終止的后果:
1、服務器資源泄露:
服務器在發送FIN后處于LAST_ACK狀態,若未收到客戶端的ACK,會反復重傳FIN(默認重試次數由tcp_orphan_retries
控制)。如果客戶端已銷毀連接,服務器將永遠無法關閉連接,導致,占用端口、內存等資源。
2、新數據連接混亂(舊報文干擾):
客戶端若立即復用相同四元組(源IP、源端口、目標IP、目標端口)建立新連接:
舊連接的延遲報文可能被新連接接收(序列號匹配時)。
服務器未關閉的舊連接可能對新連接發送的SYN返回RST(認為非法報文)。
3、破壞協議可靠性:TIME_WAIT的核心作用:
確保最后一個ACK可靠到達:等待2MSL(足夠時間讓ACK丟失后重傳FIN并響應)。
4、隔離新舊連接:通過2MSL等待,確保網絡中舊連接的報文全部過期。
5、若跳過TIME_WAIT:TCP的可靠關閉機制被破壞,可能引發不可預知的傳輸錯誤。
2.3、TIME_WAIT時間的設定
問:為什么TIME_WAIT狀態的時間是2MSL?
? ? ? ? MSL --- 報文最大生存時間
? ? ? ? 防止舊的數據包 --- 在TIME_WAIT時間過短或沒有的情況下,某次服務器發送了一組字節流因為網絡延遲,停留在鏈路中,然后雙方繼續正常交流直至斷開連接,可是在雙方都是CLOSE狀態后,之前的那組字節流被發送客戶端,而客戶端想要的下一個序列號剛好跟發過來的舊的SEQ對應上了,所以客戶端就會接收舊的數據,而報文在鏈路中最大生存時間為MSL,在定義了TIME_WAIT為2MSL就可以保證舊的數據報文在鏈路中被丟棄,而不會被誤收。
? ? ? ? 確保重傳FIN報文 --- 能保證在服務器在發送了FIN報文后沒有收到ACK確認報文,在第一個MSL中丟棄掉之前的FIN報文,然后在第二個MSL期間,服務器達到重傳時間,重傳FIN報文被客戶端接收到。
? ? ? ? 時間定義的太長,會導致占用資源過大
2.3、TCP斷開連接的次數
問:TCP的斷開連接為什么是四次而不是三次或兩次?
? ? ? ? 保證數據傳輸的完整 --- 在客戶端發送完數據后就會發送FIN報文,而此時服務器未必發完數據了,所以服務器只會回復一個ACK確認報文,等到自己的數據發送完成后,才會發送FIN報文,然后客戶端發送ACK確認報文,最終雙方斷開連接。而實際上是有可能是三次的,客戶端發送FIN報文的時候,服務器剛好也發送完了數據包。兩次的話就是有一方出現了故障。
三、TCP可靠性傳輸
確認、重傳、排序、流控(后面單起一節)
3.1、排序
? ? ? ? 分段 --- TCP是一款基于字節流的協議
? ? ? ? MSS --- 最大段長度 ---?SYN報文中會攜帶需要協商的參數,當雙方發送的MSS不一致時,最后建立連接后會按照較小的MSS來進行傳輸 --- MSS最大典型值為1460字節(如果在應用層進行分段后還需要在網絡層進行分片,這就有點浪費資源,最好就是一步到位,而MSS最大即在到達數據鏈路層的其他層最小,所以就是網絡層的最小頭部長度和傳輸層的最小頭部長度都是20字節,1500減去40,就是MSS的典型值1460字節)
? ? ? ? MTU --- 最大傳輸單元 --- 數據鏈路層所能攜帶的最大字節數,默認是1500字節,在層層封裝之后由網絡層發送給數據鏈路層的時候需要保證傳輸的數據段最大長度是1500字節
分片 --- 網絡層的IP協議執行
可以通過指定發送的數據大小來實現分片的效果,只要指定的數據大于MTU
可以通過這種方式在虛擬設備上進行一個環境復現
ping -s 指定的數據大小<20-9600> 目標IP??
????????這里可以發現數據大小是1480,這是因為在IP協議分片的的分片包中,只有第一個數據包上會攜帶IPv4頭部內容、ICMP協議內容,在后面的數據包中是只有IPV4頭部內容加上數據,所以是1480,這樣的話容易出現在傳輸過程中,但凡有一片數據丟失,到達主機后無法合成完成數據報文,但是由于數據沒有標識,目標主機要求重傳的時候只能是整個數據全部重傳,這樣效率很低,這個時候就需要依靠上層協議來進行標識,TCP協議中有一個序列號來進行標識。
序列號 --- 序列號是基于字節流的傳輸協議,會給每一個字節進行標識
3.2、確認機制
????????TCP為了保證對方能夠收到本端發送的數據段的方法,是讓對方回復一個確認報文段,這就是確認機制的做法。
確認序列號 --- 接收方期望收到發送方發送的下一個字節的序號 --- 累積確認
ACK標記位置1,然后開啟Ack Num,該值為發送的seq的值+1,代表著下一個字節的序列號
3.3、重傳機制
? ? ? ? 超時重傳 --- 本端在一定時間內,沒有收到對端反饋的確認報文觸發的重傳機制。
? ? ? ? 系統會定義一個最大重傳次數,要是一直沒有接收到反饋報文,達到次數后就關閉TCP連接(不同系統不同的次數)。
? ? ? ? RTO --- 超時重傳時間,RTO的值在計算時,需要略大于RTT,因為RTT是一個可變化的值,所以,RTO也是一個動態變化的值。
? ? ? ? RTT --- 往返時間 --- 指的是發出端將數據發出后,到ta接收到對端反饋的確認報文之后的這一段時間(由于網絡鏈路情況不定,該值也是波動的)。
如果RTO過長 --- 則會導致丟包之后,重傳的效率降低,無法及時做出響應
如果RTO過短 --- 則會導致不必要的重傳
超時間隔加倍
? ? ? ? 一般是在網絡擁塞的情況下,出現了seq或ack報文沒有發出或收到的情況下,如果發送端還是無腦的不停重傳,無疑會加重網絡中的擁塞,所以采用這種超時間隔加倍的機制,使得不去加重網絡擁塞的情況。
3.4、重傳機制的優化
快速重傳機制
? ? ? ? 失序報文 --- 接收方收到了一個數據段中的序列號大于自己期望的序列號,這樣的報文就是一個失序報文。在客戶端發送數據段的時候不是一發一等,這樣效率有點低,一般是一組一等,所以才會出現失序報文。最大數據段數 = 發送窗口 / MSS
若 rwnd=65535 字節,cwnd=10000 字節,MSS=1460 字節,則發送窗口為 10000 字節,最多可發送 6 個數據段(10000 / 1460 ≈ 6.85,向下取整)
若網絡擁塞導致 cwnd 降至 5000 字節,則發送窗口變為 5000 字節,最多可發送 3 個數據段。
rwnd:全稱?Receive Window(接收窗口),由接收方告知發送方的可用接收緩沖區大小(單位:字節),表示接收方當前還能接收多少數據。
cwnd:全稱?Congestion Window(擁塞窗口),由發送方動態調整的窗口大小(單位:字節),表示發送方根據網絡擁塞情況認為當前可安全發送的數據量。
MSS:全稱?Maximum Segment Size(最大段大小),指 TCP 數據段中能攜帶的最大有效載荷(不包含 TCP 和 IP 頭部),通常由鏈路 MTU(最大傳輸單元)減去頭部開銷得出(如 MTU=1500 時,MSS≈1460 字節)。
冗余ACK --- Duplicate ACK
發送端接收到了連續三次冗余ACK后,會觸發一個快速重傳fast retransmission
3.5、SACK
這種設計基于實踐經驗:三次冗余 ACK 能有效區分數據包丟失與網絡延遲導致的亂序。若僅收到一兩次冗余 ACK,更可能是短暫的網絡延遲,而非丟包,此時觸發重傳會增加不必要的流量。
????????由于接收端連續發送了三個冗余ACK,而疏忽了對于后續數據段的一個確認,導致后面發送端快速重傳的時候,會從缺失數據段開始直到最后的數據,全部重新發送,這浪費了資源,除了缺失數據段后面的數據段可能是無用功,所以就衍生出了選擇確認機制。
選擇確認機制 --- SACK --- 在快速重傳機制中,接收方在回復冗余ACK時,里面將攜帶自身已經接收到的數據信息,避免重復傳輸,資源浪費。SACK會在冗余ACK中存在,ta會把后續接收到的數據段標識出來
SACK的開啟需要在三次握手中進行一個協商
3.6、TCP中三次握手的可靠性
第一次握手中的SYN報文丟失:
? ? ? ? 丟失后,會按照RTO進行一個超時重傳,但是RTO是根據RTT來進行計算的,而此時是第一次建立連接,不知道RTT需要多久的時候,所以,這個時候的RTO其實就是一個定義值(不同系統不一樣,一般推薦時間是1s)
第二次握手中的SYN_ACK報文丟失:
? ? ? ? ACK報文丟失,發送端會覺得是自己的問題會觸發超時重傳,而這個數據包中還有接收端的SYN報文,所以,與此同時,接收端也會觸發超時重傳
第三次握手中的ACK報文丟失:
? ? ? ? 此時客戶端對服務器的TCP會話已經建立,而服務器沒有收到客戶端的ACK報文后,會認為是自己的SYN報文出現問題,觸發超時重傳
3.7、TCP中四次揮手的可靠性
第一個FIN報文丟失:
客戶端發送完第一個FIN報文之后,將進入到FIN_WAIT1的狀態,在這個狀態中,如果能夠正常收到服務器發送的ACK報文,則可以進入到FIN_WAIT2的狀態。
? ? ? ? 當然,如果這個數據包丟失的話,他自然也無法收到服務器反饋的ACK。則將觸發超時重傳。重傳之后收到ACK則將繼續向下切換狀態。但是,如果這個數據包持續丟失,則重傳時間將間隔翻倍,最終,達到最大重傳次數后,進入CLOSE(關閉)狀態。
第二個ACK報文段::
? ? ? ? 服務器收到FIN之后,將回復ACK報文作為應答。回復完成將進入到CLOSED_WAIT狀態。如果這個ACK報文對方沒有收到,注意,ACK報文段是不會重傳的,則客戶端將超時重傳FIN報文段。
? ? ? ? 如果,這個ACK報文段一直丟失,則客戶端重傳FIN達到最大次數后,也將進入CLOSE(關閉)狀態。
第三個FIN報文段:
? ? ? ? 服務器發送的這個FIN報文如果客戶端沒有收到,服務器也就無法收到客戶端最后回復的ACK應答,則服務器將觸發超時重傳。
? ? ? ? 如果,這個FIN報文段一直丟失,則服務器重傳FIN達到最大次數后,將進入CLOSE(關閉)狀態。
第四個ACK報文段:
? ? ? ? 其實最后一個ACK報文段的情況也是差不多的,我們專門設計了主動段開放再回復最后一個ACK之后,需要等待2MSL的機制。所以,客戶端最后一個ACK如果服務器沒有收到,則將觸發服務器的超時重傳,重傳FIN報文。
? ? ? ? 客戶端在收到這個FIN報文后,會重新反饋ACK報文,并且重置2MSL的計時器時間。
? ? ? ? 如果,這個ACK報文段一直丟失,則服務器重傳FIN達到最大次數后,將進入CLOSE(關閉)狀態。而客戶端再沒有收到新的FIN后,等待2MSL時間到達后也將進入CLOSE(關閉)狀態。
四、TCP流量控制
4.1、滑動窗口
滑動窗口 --- 窗口大小不斷的更新去適應環境,這樣的窗口流控機制,就稱為滑動窗口機制。
?窗口 --- 窗口大小指的就是無需等待確認應答,而可以連續發送的數據包的最大值,窗口大小跟建立TCP連接中分配的緩存空間有聯系。
接收窗口 --- rwnd --- 反映的是接收方此時緩存空間可用的大小 --- 因為緩存空間中存放的數據的空間是隨時間變化,所以,rwnd也是隨時間變化的,最開始時,rwnd = RcvBuffer。
緩存空間和空閑空間共同組成RcvBuffer,當成功傳輸了一組數據段后就會放到緩存空間中,導致接收空間變小,所以rwnd是一個變化值。
通告rwnd的時機:注意這里是雙向會話,兩邊都有滑動窗口機制
首次通告:三次握手階段(SYN-ACK 報文段)
????????在 TCP 三次握手的第二個報文段(接收方發送的SYN-ACK 段)中,接收方會首次向發送方通告自己的初始接收窗口大小(rwnd)。發送方根據該 rwnd 值(結合自身的擁塞窗口 cwnd)確定初始發送窗口,開始數據傳輸。
常規通告:數據確認時(ACK 報文段攜帶)
????????當接收方收到發送方的數據段后,在發送ACK 確認報文段時,會同時攜帶當前的接收窗口大小(rwnd)。
????????立即確認:若接收方啟用 “立即 ACK”(不延遲確認),則收到數據后立即發送 ACK + rwnd。
????????延遲確認:若啟用 “延遲 ACK”(如等待 ACK 累積或等待發送數據捎帶確認),則在延遲后的 ACK 中更新 rwnd(通常延遲時間不超過 200ms)。
????????更新邏輯:rwnd 的值反映接收方當前可用的接收緩沖區空間(即總緩沖區大小 - 已接收但未被上層應用讀取的數據量)。每次數據被接收或上層應用讀取后,rwnd 可能變化,并通過 ACK 實時通告給發送方。
特殊場景:窗口更新(Window Update 報文段)
????????當接收方的可用緩沖區空間(rwnd)從 0 變為非 0 時(即之前因緩沖區滿而通告 rwnd=0,后續緩沖區釋放),會主動發送一個僅包含 rwnd 字段的 ACK 報文段(稱為 “窗口更新”),通知發送方恢復數據發送。
注意:窗口更新報文段不攜帶數據確認(即 ACK 號不變化),僅用于告知發送方可用窗口已更新,避免發送方因等待超時才重新嘗試發送。
核心規則:rwnd 始終通過 ACK 報文段攜帶
????????無論何種場景,rwnd 的值永遠包含在 TCP 的 ACK 報文段中(包括 SYN-ACK、數據 ACK、窗口更新 ACK)。發送方通過解析 ACK 中的窗口字段獲取當前 rwnd,并據此調整發送窗口(取 rwnd 和 cwnd 的較小值)。
4.2、窗口關閉
窗口關閉 --- 接收方發送一個窗口值為0的數據報文段,代表此時接收方所有緩存空間均已占滿,無法再進行數據接收,則收到后,發送方將不再發送數據
????????此時會出現一個問題,當服務器發送了一個win=0的報文表示目前的緩存空間已經占滿,客戶端就會關閉窗口,不再發送數據,但是之后服務器處理完數據,再次在ACK報文中發送自己能接收的窗口大小丟失(ACK報文丟失是不會觸發超時重傳的,之前的超時重傳是因為發送了SYN報文而太久沒收到ACK報文而觸發的超時重傳)。
應對舉措:客戶端周期性的發送一個窗口探測報文(只包含一個字節),要是服務器的窗口大小還是為0,就繼續周期性等待,要是服務器發送過來的rwnd大于0,則客戶端就開始繼續發送數據段。
????????當接收方通告小窗口比如說10字節,而TCP/IP的頭部都有40字節,這是一種資源的浪費,里面都無法攜帶數據,達不到發送數據要求的窗口就沒有意義,稱之為經濟性很差。
4.3、小窗口的處理
接收方 --- 設定一個通告窗口的最小值,一般是MSS和1/2緩存空間中的較小值,如果通告的窗口值小于設定值,則會通告一個窗口值為0的數據報文來關閉窗口,直到窗口大小突破最小值之后,再打開窗口。
發送方 --- 一般采用延時處理,只有滿足以下兩個條件之一,才發送數據,否則將囤積數據,直到滿足條件為止
? ? ? ? 1、要等到窗口大小 >= MSS? 并且數據大小 >= MSS
? ? ? ? 2、收到之前發送數據的ACK回報
五、TCP擁塞控制
TCP的擁塞控制 --- TCP也會觀察網絡的堵塞情況,如果網絡堵塞嚴重,則降低發送量,以緩解網絡的擁塞情況。
? ? ? ? 1、TCP該如何判斷此時網絡環境擁塞
? ? ? ? 2、TCP該如何控制數據的發送量?
5.1、TCP的擁塞判斷
TCP將連接中的丟包行為,視為擁塞的表現:
? ? ? ? 1、數據包確認超時(超時重傳)
? ? ? ? 2、收到接收方發送的3個冗余ACK(快速重傳)
5.2、TCP的擁塞控制
擁塞窗口 --- cwnd
接收窗口 --- rwnd
發送方,發出未收到確認的字節數必須小于或等于rwnd或cwnd中的較小值
TCP擁塞控制算法(自計時的協議) --- 慢啟動、擁塞避免、快速恢復
5.3、慢啟動
慢啟動 --- slow - start,在TCP建立連接的時候的算法,最開始的時候設置為1MSS,每收到一個正常ACK(不包含重傳ACK),cwnd就會增加一個MSS大小
慢啟動增長的速率:指數增長,這樣的增長速率對于網絡鏈路是不友好的。
慢啟動門限 --- ssthresh --- cwnd < ssthresh --- 慢啟動算法
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?cwnd >?ssthresh --- 擁塞避免算法
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?cwnd =?ssthresh --- 慢啟動算法/擁塞避免算法
5.4、擁塞避免
? ? ? ? 擁塞避免算法就是在一個RTT(往返時間)時間內,cwnd只增長一個MSS,盡可能降低網絡擁塞,而不是直接避免網絡擁塞。
5.5、快速恢復
數據包確認超時:
? ? ? ? 發送超時重傳,首先先將ssthresh(慢啟動門限)設置為當前cwnd值的一半,之后,將cwnd設置為一個MSS,按照新的門限值,執行慢啟動算法,達到門限后,重新進入到擁塞避免算法
收到接收方發送的3個冗余ACK:
? ? ? ? 實施快速恢復。首先將慢啟動門限值設置為當前擁塞窗口(發送擁塞事件時候的擁塞窗口)的一半,之后,直接基于慢啟動門限執行擁塞避免算法