【網絡通信 -- 直播】網絡通信協定簡介 -- TCP 傳輸控制協定
【1】TCP 的特點
TCP提供一種面向連接配接的、可靠的位元組流服務;
面向連接配接意味着 : 兩個使用TCP的應用在彼此交換資料之前必須先建立一個TCP連接配接,在一個TCP連接配接中,僅有兩方進行彼此通信
TCP提供可靠性保證的方式:
1. 應用資料被分割成TCP認為最适合發送的資料塊
2. 當TCP發出一個段後,它啟動一個定時器,等待目的端确認收到這個封包段,如果不能及時收到一個确認,将重發這個封包段;
3. 當TCP收到發自TCP連接配接另一端的資料,它将發送一個确認,這個确認不是立即發送,通常将推遲幾分之一秒;
4. TCP将保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化,如果收到段的檢驗和有差錯,TCP将丢棄這個封包段和不确認收到此封包段(希望發端逾時并重發);
5. 既然TCP封包段作為IP資料報來傳輸,而IP資料報的到達可能會失序,是以TCP封包段的到達也可能會失序,如果必要,TCP将對收到的資料進行重新排序,将收到的資料以正确的順序交給應用層;
6. 既然IP資料報會發生重複,TCP的接收端必須丢棄重複的資料;
7. TCP還能提供流量控制,TCP連接配接的每一方都有固定大小的緩沖空間,TCP的接收端隻允許另一端發送接收端緩沖區所能接納的資料,這将防止較快主機緻使較慢主機的緩沖區溢出;
【2】TCP 的部首

1) 16 位端口号 : 告知主機該封包段是來自哪裡(源端口Source Port) 以及傳給哪個上層協定或應用程式 (目的端口 Destination Port) 的,進行 TCP 通信時,用戶端通常使用系統自動選擇的臨時端口号,而伺服器則使用知名服務端口号(比如 DNS 協定對應端口 53,HTTP 協定對應 80,這些端口号可在 /etc/services 檔案中找到);
2) 32位序号 : 一次 TCP 通信 (從 TCP 連接配接建立到斷開) 過程中某一個傳輸方向上的位元組流的每個位元組的編号,假設主機 A 和主機 B 進行 TCP 通信,A 發送給 B 的第一個 TCP 封包段中,序号值被系統初始化為某個随機值 ISN (Initial Sequence Number,初始序号值), 那麼在該傳輸方向上 (從 A 到 B),後續的 TCP 封包段中序号值将被系統設定成 ISN 加上該封包段所攜帶資料的第一個位元組在整個位元組流中的偏移;例如,某個 TCP 封包段傳送的資料是位元組流中的第 1025~2048 位元組,那麼該封包段的序号值就是 ISN+1025;另外一個傳輸方向(從 B 到 A)的 TCP 封包段的序号值也具有相同的含義;
3) 32位确認号(acknowledgement number) : 用作對另一方發送來的 TCP 封包段的響應;其值是收到的 TCP 封包段的序号值加 1;假設主機 A 和主機 B 進行 TCP 通信,那麼 A 發送出的 TCP 封包段不僅攜帶自己的序号,而且包含對 B 發送來的 TCP 封包段的确認号;反之,B 發送出的 TCP 封包段也同時攜帶自己的序号和對 A 發送來的封包段的确認号;
4) 4位頭部長度(header length) : 辨別該 TCP 頭部有多少個 32bit 字(4位元組);因為4位最大能辨別15,是以 TCP 頭部最長是 60 位元組;
5) 6位标志位包含如下幾項 :
URG标志,表示緊急指針(urgent pointer)是否有效;
ACK标志,表示确認号是否有效;我們稱攜帶 ACK 辨別的 TCP 封包段為确認封包段
PSH标志,提示接收端應用程式應該立即從 TCP 接收緩沖區中讀走資料,為接收後續資料騰出空間(如果應用程式不将接收到的資料讀走,它們就會一直停留在 TCP 接收緩沖區中);
RST标志,表示要求對方重建立立連接配接,稱攜帶 RST 标志的 TCP 封包段為複位封包段;
SYN标志,表示請求建立一個連接配接,稱攜帶 SYN 标志的 TCP 封包段為同步封包段;
FIN标志,表示通知對方本端要關閉連接配接了,稱攜帶 FIN 标志的 TCP 封包段為結束封包段;
6) 16位視窗大小(window size) : 是 TCP 流量控制的一個手段,此處的視窗,指的是接收通告視窗(Receiver Window,RWND),告訴對方本端的 TCP 接收緩沖區還能容納多少位元組的資料,這樣對方就可以控制發送資料的速度;
7) 16位校驗和(TCP check sum) : 由發送端填充,接收端對 TCP 封包段執行 CRC 算法以檢驗 TCP 封包段在傳輸過程中是否損壞;注意,這個校驗不僅包括TCP頭部,也包括資料部分,這也是 TCP 可靠傳輸的一個重要保障;
8) 16位緊急指針(urgent pointer) : 是一個正的偏移量,它和序号字段的值相加表示最後一個緊急資料的下一位元組的序号;是以,确切地說,這個字段是緊急指針相對目前序号的偏移,不妨稱之為緊急偏移;TCP 的緊急指針是發送端向接收端發送緊急資料的方法;
9) TCP 頭部選項 : TCP 頭部的最後一個選項字段(options)是可變長的可選資訊;
kind=0 是選項表結束選項;
kind=1 是空操作(nop)選項,沒有特殊含義,一般用于将 TCP 選項的總長度填充為 4 位元組的整數倍;
kind=2 是最大封包段長度選項,TCP 連接配接初始化時,通信雙方使用該選項來協商最大封包段長度(Max Segement Size,MSS),TCP 子產品通常将 MSS 設定為(MTU-40)位元組(減掉的這 40 位元組包括 20 位元組的 TCP 頭部和 20 位元組的 IP 頭部),這樣攜帶 TCP 封包段的 IP 資料報的長度就不會超過 MTU (假設 TCP 頭部和 IP 頭部都不包含選項字段,并且這也是一般情況),進而避免本機發生 IP 分片;對以太網而言,MSS 值是 1460 (1500-40)位元組;
kind=3 是視窗擴大因子選項,TCP 連接配接初始化時,通信雙方使用該選項來協商接收通告視窗的擴大因子;在 TCP 的頭部中,接收通告視窗大小時用 16 位表示的,故最大為 65535 位元組,但實際上 TCP 子產品允許的接收通告視窗大小遠不止這個數 (為了提高 TCP 通信的吞吐量),視窗擴大因子解決了這個問題;假設 TCP 頭部中的接收通告視窗大小是 N 乘 2 的 M 次方,或者說 N 左移 M 位;注意,M 的取值範圍是 0~14,可以通過修改 /proc/sys/net/ipv4/tcp_window_scaling 核心變量來啟用或關閉視窗擴大因子選項;
kind=5 是 SACK 實際工作的選項,該選項的參數告訴發送方本端已經收到并緩存的不連續的資料塊,進而讓發送端可以據此檢查并重發丢失的資料塊;每個塊邊沿(edge of block) 參數包含一個 4 位元組的序号;其中塊左邊沿表示不連續塊的第一個資料的序号,而塊右邊沿則表示不連續塊的最後一個資料的序号的下一個序号;這樣一對參數 (塊左邊沿和塊右邊沿) 之間的資料是沒有收到的;因為一個塊資訊占用 8 位元組,是以 TCP 頭部選項中實際上最多可以包含 4 個這樣的不連續資料塊(考慮選項類型和長度占用的 2 位元組);
kind=8 是時間戳選項,該選項提供了較為準确的計算通信雙方之間的回路時間 (Round Trip Time,RTT) 的方法,進而為 TCP 流量控制提供重要資訊,我們可以通過修改 /proc/sys/net/ipv4/tcp_timestamps 核心變量來啟用或關閉時間戳選項;
【3】TCP 狀态轉移與連接配接的建立、中止
1. 對于建連結的3次握手:主要是要初始化 Sequence Number 的初始值,通信的雙方要互相通知對方自己的初始化的 Sequence Number(縮寫為ISN:Inital Sequence Number),SYN(Synchronize Sequence Numbers)即上圖中的 x 和 y,這個号要作為以後的資料通信的序号,以保證應用層接收到的資料不會因為網絡上的傳輸的問題而亂序(TCP會用這個序号來拼接資料);
2. 對于4次揮手:其實是2次,因為TCP是全雙工的,發送方和接收方都需要 Fin 和 Ack,隻不過,有一方是被動的,是以看上去就成了所謂的4次揮手;
3. 關于建連接配接時SYN逾時:如果server端接到了clien發的SYN後回了SYN-ACK後client掉線了,server端沒有收到client回來的ACK,那麼,這個連接配接處于一個中間狀态,既沒成功,也沒失敗;于是,server端如果在一定時間内沒有收到的TCP會重發SYN-ACK,在Linux下,預設重試次數為5次,重試的間隔時間從1s開始每次都翻倍,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s都知道第5次也逾時了,是以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才會把斷開這個連接配接;
4. 最大封包段長度(MSS):TCP傳往另一端的最大塊資料的長度,當一個連接配接建立時,連接配接的雙方都要通告各自的MSS;
5. 在TCP的狀态圖中,從TIME_WAIT狀态到CLOSED狀态,有一個逾時設定,這個逾時設定是 2*MSL,為什麼要這有TIME_WAIT?主要有兩個原因:1)TIME_WAIT確定有足夠的時間讓對端收到了ACK,如果被動關閉的那方沒有收到Ack,就會觸發被動端重發Fin,一來一去正好2個MSL,2)有足夠的時間讓這個連接配接不會跟後面的連接配接混在一起【詳見 【Linux網絡程式設計】TCP連接配接的分組交換與狀态轉移】;
6. 平靜時間的概念:TCP在重新開機動後的MSL秒内不能建立任何連接配接,問題描述,如果使用處于2MSL等待端口的主機出現故障,它會在MSL秒内重新啟動,并立即使用故障前仍處于2MSL的插口對來建立一個新的連接配接,若如此則在故障前從這個連接配接發出而遲到的封包段會被錯誤地當作屬于重新開機後新連接配接的封包段;
難點總結
1. TCP 連接配接的初始化序列号能否固定
ISN(Inital Sequence Number)和一個假的時鐘綁在一起,這個時鐘會在每4微秒對ISN做加一操作,直到超過2^32,又從0開始,這需要4小時才會産生ISN的回繞問題,這幾乎可以保證每個新連接配接的ISN不會和舊的連接配接的ISN産生沖突,現在的實作大多是在一個基準值的基礎上進行随機的;
2. TIME_WAIT 帶來的問題
TIME_WAIT帶來的問題主要源于,一個連接配接進入TIME_WAIT狀态後需要等待2*MSL(一般是1到4分鐘)的時間才能斷開連接配接釋放連接配接占用的資源,是以會造成以下問題:
- 1) 作為伺服器,短時間内關閉了大量的Client連接配接,就會造成伺服器上出現大量的TIME_WAIT連接配接,嚴重消耗着伺服器的資源;
- 2) 作為用戶端,短時間内大量的短連接配接,會大量消耗的Client機器的端口,畢竟端口隻有65535個,端口被耗盡了,後續将無法在發起新的連接配接;
3. TIME_WAIT的快速回收和重用
TIME_WAIT的快速回收
linux下開啟TIME_WAIT快速回收需要同時打開tcp_tw_recycle和tcp_timestamps(預設打開)兩選項,Linux下快速回收的時間為3.5 * RTO(Retransmission Timeout),而一個RTO時間為200ms至120s;
拒絕新連接配接需要同時滿足以下三個條件
- 1)來自同一個對端Peer的TCP包攜帶了時間戳;
- 2)之前同一台peer機器(僅僅識别IP位址,因為連接配接被快速釋放了,沒了端口資訊)的某個TCP資料在MSL秒之内到過本Server;
- 3)Peer機器新連接配接的時間戳小于peer機器上次TCP到來時的時間戳,且內插補點大于重放視窗戳(TCP_PAWS_WINDOW);
特殊情況 : 在一個NAT後面的所有Peer機器在Server看來都是一個機器,NAT後面的那麼多Peer機器的系統時間戳很可能不一緻,有些快,有些慢,這樣,在Server關閉了與系統時間戳快的Client的連接配接後,在這個連接配接進入快速回收的時候,同一NAT後面的系統時間戳慢的Client向Server發起連接配接,這就很有可能同時滿足上面的三種情況,造成該連接配接被Server拒絕掉;
TIME_WAIT的重用
隻要滿足下面兩點中的一點,一個TW狀态的四元組(即一個socket連接配接)可以重新被新到來的SYN連接配接使用:
- 1)新連接配接SYN告知的初始序列号比TIME_WAIT老連接配接的末序列号大;
- 2)如果開啟了tcp_timestamps,并且新到來的連接配接的時間戳比老連接配接的時間戳大;
注意 :要同時開啟tcp_tw_reuse選項和tcp_timestamps 選項才可以開啟TIME_WAIT重用;
tcp_tw_reuse 和 SO_REUSEADDR 選項對比
兩者是兩個完全不同的概念,tcp_tw_reuse是核心選項,而SO_REUSEADDR使用者态的選項,使用SO_REUSEADDR是告訴核心,如果端口忙,但TCP狀态位于 TIME_WAIT ,可以重用端口;如果端口忙,而TCP狀态位于其他狀态,重用端口時依舊得到一個錯誤資訊, 指明Address already in use”;
【3.1】TCP 的半關閉
TCP的半關閉:TCP提供的連接配接的一端在結束它的發送後還能接收來自另一端資料的能力;
【3.2】TCP 雙方同時建立連接配接
【3.3】TCP 雙方同時關閉連接配接
【4】TCP 的互動資料流
【4.1】經受時延的确認
通常TCP在接收到資料時并不立即發送ACK,相反,它推遲發送,以便将ACK與需要沿該方向發送的資料一起發送(這種現象為資料捎帶ACK),絕大多數實作采用的時延為200 ms,也就是說,TCP将以最大200 ms的時延等待是否有資料一起發送;
難點總結
1. TCP的延遲确認機制
按照TCP協定,确認機制是累積的,也就是确認号X的确認指的是所有X之前但不包括X的資料已經收到了;确認号(ACK)本身就是不含資料的分段,是以大量的确認号消耗了大量的帶寬,雖然大多數情況下,ACK還是可以和資料一起捎帶傳輸的,但是如果沒有捎帶傳輸,那麼就隻能單獨回來一個ACK,如果這樣的分段太多,網絡的使用率就會下降;延遲的ACK即ACK在收到資料後并不馬上回複,而是延遲一段可以接受的時間,看能不能和接收方要發給發送方的資料一起回去,因為TCP協定頭中總是包含确認号的,如果能的話,就将資料一起捎帶回去,這樣網絡使用率就提高了;
延遲ACK就算沒有資料捎帶,那麼如果收到了按序的兩個包,那麼隻要對第二包做确認即可,這樣也能省去一個ACK消耗;由于TCP協定不對ACK進行ACK的,RFC建議最多等待2個包的積累确認,這樣能夠及時通知對端Peer,我這邊的接收情況;
ACK分段的确認号,是确認按序收到的最後一個位元組序,對于亂序到來的TCP分段,接收端會回複相同的ACK分段,隻确認按序到達的最後一個TCP分段,TCP連接配接的延遲确認時間一般初始化為最小值40ms,随後根據連接配接的重傳逾時時間(RTO)、上次收到資料包與本次接收資料包的時間間隔等參數進行不斷調整;
【4.2】Nagle 算法
Nagle 算法要求一個TCP連接配接上最多隻能有一個未被确認的未完成的小分組,在該分組的确認到達之前不能發送其他的小分組,相反,TCP收集這些少量的分組,并在确認到來時以一個分組的方式發出去,該算法的優越之處在于它是自适應的:确認到達得越快,資料也就發送得越快;
【5】TCP 的成塊資料流
【5.1】快的發送方與慢的接收方
【6】TCP 的流量控制
【6.1】滑動視窗
TCP頭裡有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端自己還有多少緩沖區可以接收資料,于是發送端就可以根據這個接收端的處理能力來發送資料,而不會導緻接收端處理不過來;
1. 接收端LastByteRead指向了TCP緩沖區中讀到的位置,NextByteExpected指向的地方是收到的連續包的最後一個位置,LastByteRcved指向的是收到的包的最後一個位置,中間有些資料還沒有到達,存在資料空白區;
2. 發送端的LastByteAcked指向了被接收端Ack過的位置(表示成功發送确認),LastByteSent表示發出去了,但還沒有收到成功确認的Ack,LastByteWritten指向的是上層應用正在寫的地方;
3. 接收端在給發送端回ACK中會彙報自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
4. 發送方會根據這個視窗來控制發送資料的大小,以保證接收方可以處理;
視窗左右邊沿移動的術語
1. 視窗合攏,視窗左邊沿向右邊沿靠近,這種現象發生在資料被發送和确認時;
2. 視窗張開,當視窗右邊沿向右移動時将允許發送更多的資料,這種現象發生在另一端的接收程序讀取已經确認的資料并釋放了TCP的接收緩存時;
3. 視窗收縮,視窗右邊沿向左移動;
滑動視窗移動圖示
滑動窗滑動後的示意圖
#1 已收到ack确認的資料;
#2 發還沒收到ack的;
#3 在視窗中還沒有發出的(接收方還有空間);
#4 視窗以外的資料(接收方沒空間);
【6.2】接受端控制發送端的圖示
Zero Window
若Window變成0了,TCP發送端就不發資料了,TCP使用Zero Window Probe技術,縮寫為ZWP,探測視窗大小是否不為0,即發送端在視窗變成0後,會發ZWP的包給接收方,讓接收方來ack其Window尺寸,一般這個值會設定成3次,每次大約30-60秒,如果3次過後還是0的話,有的TCP實作就會發RST端口連結;
Silly Window Syndrome
1. 如果這個問題是由Receiver端引起的,則在receiver端,如果收到的資料導緻window size小于某個值,可以直接ack(0)回sender,這樣就把window給關閉了,也阻止了sender再發資料過來,等到receiver端處理了一些資料後windows size 大于等于了MSS,或者 receiver buffer有一半為空,就可以把window打開讓send 發送資料過來;
2. 如果這個問題是由Sender端引起的,那麼采用 Nagle’s algorithm,這個算法的思路也是延時處理,兩個主要的條件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前發送資料的ack回包,才會發資料,否則就是在攢資料;
【7】TCP 的重傳機制
【7.1】TCP 的重傳逾時計算
為了使重傳機制更高效,如果能夠比較準确知道在目前網絡狀況下,一個資料包從發出去到回來的時間 RTT——Round Trip Time,那麼根據該 RTT 就可以友善設定TimeOut——RTO(Retransmission TimeOut);
RTO 的經典算法
1)首先采樣計算RTT值;
2)然後計算平滑的RTT,稱為Smoothed Round Trip Time (SRTT),SRTT = ( ALPHA*SRTT ) + ((1-ALPHA)*RTT);
3)RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]];
其中:UBOUND是RTO值的上限;LBOUND是RTO值的下限;ALPHA 平滑因子(e.g., .8 to .9), BETA 延遲方差因子(e.g., 1.3 to 2.0);
改進算法 Jacobson / Karels Algorithm
SRTT = SRTT + α (RTT – SRTT) —— 計算平滑RTT;
DevRTT = (1-β)DevRTT + β(|RTT-SRTT|) ——計算平滑RTT和真實的差距(權重移動平均);
RTO= μ SRTT + ∂ DevRTT
(其中:在Linux下,α = 0.125,β = 0.25, μ = 1,∂ = 4)
每一個TCP連接配接單一逾時定時器的設計的算法規則
1)每一次一個包含資料的包被發送(包括重發),如果還沒開啟重傳定時器,則開啟它,使得它在RTO秒之後逾時(按照目前的RTO值);
2)當接收到一個ACK确認一個新的資料, 如果所有的發出資料都被确認了,關閉重傳定時器;
3)當接收到一個ACK确認一個新的資料,還有資料在傳輸,也就是還有沒被确認的資料,重新啟動重傳定時器,使得它在RTO秒之後逾時(按照目前的RTO值);
4)當重傳定時器逾時後,依次做下列3件事情:
4.1)重傳最早的尚未被TCP接收方ACK的資料包
4.2)重新設定RTO 為 RTO * 2(“還原定時器”),但是新RTO不應該超過RTO的上限(RTO有個上限值,這個上限值最少為60s)
4.3)重新開機重傳定時器
上面的建議算法展現了一個原則:沒被确認的包必須可以逾時,并且逾時的時間不能太長,同時也不要過早重傳;
【7.2】TCP 的重傳機制
Selective Acknowledgment (SACK,選擇确認)機制,SACK是TCP的擴充選項,包括:
- 1)SACK允許選項(Kind=4,Length=2,選項隻允許在有SYN标志的TCP包中);
- 2)SACK資訊選項(Kind=5,Length);
D-SACK 利用第一塊SACK資料中描述重複接收的不連續資料塊的序列号參數,其他SACK資料則描述其他正常接收到的不連續資料,這樣發送方利用第一塊SACK,可以發現資料段被網絡複制、錯誤重傳、ACK丢失引起的重傳、重傳逾時等異常的網絡狀況,使得發送端能更好調整自己的重傳政策;
D-SACK 的優點:
- 1)發送端可以判斷出,是發包丢失了,還是接收端的ACK丢失了;(發送方,重傳了一個包,發現并沒有D-SACK那個包,那麼就是發送的資料包丢了;否則就是接收端的ACK丢了,或者是發送的包延遲到達了);
- 2)發送端可以判斷自己的RTO是不是有點小了,導緻過早重傳(如果收到比較多的D-SACK就該懷疑是RTO小了);
- 3)發送端可以判斷自己的資料包是不是被複制了(如果明明沒有重傳該資料包,但是收到該資料包的D-SACK);
- 4)發送端可以判斷目前網絡上是不是出現了有些包被delay了,也就是出現先發的包卻後到了;
【8】TCP 擁塞的處理
【8.1】網絡阻塞的原因
對于TCP,端到端的流量控制必然會導緻網絡擁堵,這是因為TCP隻看到對端的接收空間的大小,而無法知道鍊路上的容量,隻要雙方的處理能力很強,那麼就可以以很大的速率發包,于是鍊路很快出現擁堵,進而引起大量的丢包,丢包又引發發送端的重傳風暴,進一步加劇鍊路的擁塞;
鍊路上的轉發節點,例如路由器,再好的路由器隻要接入網絡,總是會拉低網絡的總帶寬,如果在路由器節點上出現處理瓶頸,那麼就很容易出現擁塞;
【8.2】備援ACK的判定條件(對于SACK,那麼根據SACK的一些資訊來進一步判斷)
[1] 接收ACK的那端已經發出了一些還沒被ACK的資料包
[2] 該ACK沒有捎帶data
[3] 該ACK既不是SYN包的ACK也不是FIN包的ACK
[4] 該ACK的确認号等于接收ACK那端已經收到的ACK的最大确認号
[5] 該ACK通知的視窗等于接收該ACK的那端上一個收到的ACK的視窗
【8.3】Reno 算法包含4個部分
[1]慢熱啟動算法 – Slow Start;
[2]擁塞避免算法 – Congestion Avoidance;
[3]快速重傳 - Fast Retransimit;
[4]快速恢複算法 – Fast Recovery;
【8.3.1】慢啟動
慢啟動算法流程
- 1)連接配接建好先初始化cwnd = N,表明可以傳N個MSS大小的資料;
- 2)每當收到一個ACK,cwnd++; 呈線性上升;
- 3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升;
- 4)ssthresh(slow start threshold)是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免算法”;
根據 RFC5681,如果MSS > 2190 bytes,則N = 2; 如果MSS < 1095 bytes,則N = 4; 如果2190 bytes >= MSS >= 1095 bytes,則N = 3; 一篇Google的論文《An Argument for Increasing TCP’s Initial Congestion Window》建議把cwnd 初始化成了 10個MSS,Linux 3.0後采用了這篇論文的建議。
【8.3.2】擁塞避免
擁塞避免算法流程
- 1)收到一個ACK時,cwnd = cwnd + 1/cwnd
- 2)當每過一個RTT時,cwnd = cwnd + 1
【8.3.3】擁塞發生
TCP是看不到網絡的整體狀況的,那麼TCP認為網絡擁塞的主要依據是它重傳了封包段;
擁塞發生處理流程
1)出現RTO逾時,重傳資料包,這種情況下,TCP就認為出現擁塞的可能性就很大:
- sshthresh = cwnd /2
- cwnd 重置為 1
- 進入慢啟動過程
2)在RTO逾時前,收到3個duplicate ACK進行重傳資料包,這種情況下,收到3個備援ACK後說明确實有中間的分段丢失,然而後面的分段确實到達了接收端,因為這樣才會發送備援ACK,這一般是路由器故障或者輕度擁塞或者其它不太嚴重的原因引起的,是以此時擁塞視窗縮小的幅度就不能太大,此時進入快速重傳;
【8.3.4】快速重傳
- 1) 調整門限ssthresh的值為目前cwnd值的1/2;
- 2) 将cwnd值設定為新的ssthresh的值;
- 3) 重新進入擁塞避免階段;
在快速重傳的時候,一般網絡隻是輕微擁堵,在進入擁塞避免後,cwnd恢複的比較慢,針對這個,“快速恢複”算法被添加進來,當收到3個備援ACK時,TCP最後的[3]步驟進入的不是擁塞避免階段,而是快速恢複階段;
【8.3.5】快速恢複
快速恢複算法流程
- cwnd = sshthresh + 3 * MSS (3的意思是确認有3個重傳Duplicated ACKs指定的資料包被收到了)
- 如果再收到 duplicated Acks,那麼cwnd = cwnd +1
- 如果收到了新的Ack,那麼,cwnd = 快速重傳階段1的 sshthresh 值,然後就進入擁塞避免的算法
New Reno算法,該算法是在沒有SACK的支援下改進Fast Recovery算法(SACK改變TCP的确認機制,把亂序等資訊會全部告訴對方,SACK本身攜帶的資訊就可以使得發送方有足夠的資訊來知道需要重傳哪些包,而不需要重傳哪些包)
- 1)發送端收到3個備援ACK後,重傳備援ACK訓示可能丢失的那個包segment1,如果segment1的ACK通告接收端已經收到發送端的全部已經發出的資料,那麼就是隻丢失一個包,如果沒有,那麼就是有多個包丢失了;
- 2)發送端根據segment1的ACK判斷出有多個包丢失,那麼發送端繼續重傳視窗内未被ACK的第一個包,直到sliding window内發出去的包全被ACK了,才真正退出Fast Recovery階段;
參考緻謝
本部落格為部落客的學習實踐總結,并參考了衆多部落客的博文,在此表示感謝,部落客若有不足之處,請批評指正。
【1】TCP/IP詳解 卷1:協定
【2】[通俗易懂]深入了解TCP協定(上):理論基礎
【3】[通俗易懂]深入了解TCP協定(下):RTT、滑動視窗、擁塞處理
【4】不為人知的網絡程式設計(一):淺析TCP協定中的疑難雜症(上篇)
【5】不為人知的網絡程式設計(二):淺析TCP協定中的疑難雜症(下篇)