大家好,我是風箏
前兩天分享了 20張圖說清楚 IP 協定
今天,繼續來網管的自我修養之TCP協定,這可是除 IP 協定外另一個核心協定了。
TCP 協定是網路傳輸中至關重要的一個協定,它位於傳輸層。向上支援 FTP、TELNET、SMTP、DNS、HTTP等常見的應用層協定,向下要與網路層的 IP 協定相互配合,實現可靠的網路傳輸。
為了讓全世界的計算機有效的互聯起來,國際標準化組織提出了一種概念化的網路模型,開放式系統互聯模型(Open System Interconnection Model),簡稱 OSI 模型。
自上而下依次為應用層、表示層、對談層、傳輸層、網路層、資料鏈路層、物理層。
應用層提供為應用軟體而設計的介面,以設定與另一應用軟體之間的通訊。例如:HTTP、HTTPS、FTP、Telnet、SSH、SMTP、POP3等。
表示層把資料轉換為能與接收者的系統格式相容並適合傳輸的格式。
對談層負責在資料傳輸中設定和維護計算機網路中兩臺計算機之間的通訊連線。
傳輸層把傳輸表頭(TH)加至資料以形成封包。傳輸表頭包含了所使用的協定等傳送資訊。例如:傳輸控制協定(TCP)等。
網路層決定資料的路徑選擇和轉寄,將網路表頭(NH)加至封包,以形成分組。網路表頭包含了網路資料。例如:網際網路協定(IP)等。
資料鏈路層負責網路定址、錯誤偵測和改錯。當表頭和表尾被加至封包時,會形成資訊框。資料連結串列頭(DLH)是包含了實體地址和錯誤偵測及改錯的方法。資料連結串列尾(DLT)是一串指示封包末端的字串。例如乙太網、無線區域網(Wi-Fi)和通用分組無線服務(GPRS)等。
分為兩個子層:邏輯鏈路控制(logical link control,LLC)子層和媒介存取控制(Media access control,MAC)子層。
物理層在區域性區域網上傳送資料框,它負責管理電腦通訊裝置和網路媒體之間的互通。包括了針腳、電壓、線纜規範、集線器、中繼器、網路卡、主機介面卡等。
OSI 模型是國際標準模型,是指導網際網路模型的概念標準。而在實際的設計實現過程中,最後形成了 TCP/IP 4層模型結構。
TCP/IP 模型實際上並不單單指 TCP 和 IP,實際上這一個協定簇,還包含了其他的一些協定,比如 UDP、ICMP、IGMP 等。
TCP/IP 模型是事實上的標準模型,在 7 層模型的基礎上將最上面三層的應用層、表示層、對談層統一為應用層,將資料鏈路層和物理層統一為鏈路層或者叫網路介面層。
實際應用中還是以 4 層模型為準,畢竟這才是事實上的標準。還有一種 5 層模型的說法,實際上就是把 7 層中的應用層、表示層、對談層合併為應用層,其他層保持不變。
TCP/IP 模型每個層都有各自的功能和分工,當有使用者資料想要傳送給另一臺裝置的時候,資料自上而下,從應用層向鏈路層傳遞有一個複雜的過程。
以 Telnet 為例,Telnet 在傳輸層是使用 TCP 協定的。
資料從應用層進入,到達傳輸層,新增上 TCP首部,將資料加工成 TCP 段,稱為 Segment。這是為了保證資料的可靠性。
接著資料到達網路層,在網路層使用 IP 協定,被新增上 IP 首部,將資料加工成 IP資料包,稱為 datagram 。經過網路層 IP 協定的加工,指定目標地址和 MAC 地址,保證資料準確的傳送到目標機器。
接著資料到達鏈路層,新增上乙太網頭部,將資料加工成乙太網幀,稱為 frame,包含了網路卡等硬體相關的資料。
無論是 Telnet 還是 HTTP,都至少涉及到兩臺裝置才能稱之為網路互連,那傳送方有一個資料自應用層向底層鏈路層的加工過程,對應的,在資料接收方,有一個資料從鏈路層嚮應用層解析的過程。這中間可能經歷了漫長的傳輸媒介,比如光纖,還可能有若干個中間裝置,比如路由器、交換機等等。要保證資料在這麼複雜的網路環境中可靠、準確的傳送到目標機器,就是靠的 TCP、IP協定精巧的設計。
TCP,全稱是 Transmission Control Protocol,傳輸控制協定。 是一種面向連線的、可靠的位元組流服務協定,正因為它要保證可靠性,所以比起 UDP 協定要複雜的多,正是由於這種複雜性,導致它的效能比 UDP 差。
TCP 是 TCP/IP 模型中的傳輸層一個最核心的協定,不僅如此,在整個 4 層模型中,它都是核心的協定,要不然模型怎麼會叫做 TCP/IP 模型呢。
它向下使用網路層的 IP 協定,向上為 FTP、SMTP、POP3、SSH、Telnet、HTTP 等應用層協定提供支援。其他的還有我們常用的 Redis 的 RESP 協定、MongoDB的網路協定,以及我們程式設計中用到的 Socket,都是 TCP 協定在背後提供支援的。
網路協定是通訊計算機雙方必須共同遵從的一組約定。如怎麼樣建立連線、怎麼樣互相識別等。只有遵守這個約定,計算機之間才能相互通訊交流。它的三要素是:語法、語意、時序。
語法:即資料與控制資訊的結構或格式;
語意:即需要發出何種控制資訊,完成何種動作以及做出何種響應;
時序(同步):即事件實現順序的詳細說明。
TCP首部 + 使用者資料被稱為TCP段,其中 TCP 首部就是這裡要主要研究的 TCP 協定的核心所在,使用者資料部分是 TCP 段的負載。
TCP 段的大小也是有限制的,最大是 1460 位元組,這是怎麼算出的呢?
最終由網路卡發出去的封包叫做乙太網幀,乙太網幀由乙太網首部和負載構成。
乙太網幀的負載就是一個 IP 資料包,IP資料包由IP首部和負載構成。
IP資料包的負載就是一個 TCP段。所以,TCP段所能搭載的最巨量資料量可以這樣計算出來:
$TCP段搭載的資料大小 = 乙太網幀大小-乙太網首部-IP首部-TCP首部$
乙太網幀的大小是固定的 1522位元組,而IP首部和TCP首部的大小是不固定的,但是最少會各佔20位元組,所以最後算下來 TCP段搭載的資料大小最多為 1460位元組。
$TCP段搭載的資料大小(最多1460) = 乙太網幀大小(1522位元組)-乙太網首部(22位元組)-IP首部(最少20位元組)-TCP首部(最少20位元組)$
下圖是TCP協定的示意圖,如果不算「可選項」部分的話,共佔用 32bit x 5 = 160bit
,也就是20個位元組。
源埠和目標埠分別佔用 2個位元組,共佔用 4 位元組,分別記錄資料傳送端的埠號和資料接收端的埠號,這兩個標記和 IP 協定中記錄的傳送端 IP 和接收端 IP組合起來,便可確定一個唯一的 TCP 連線。
由於TCP段的大小有限制,當要傳輸的資料量大於這個限制的時候,就要對資料進行分段,一段一段的傳送,既然傳送方要分段,那接收方就要對分段進行重組,才能還原回原始資料。在重組的過程中,要保證各段間的先後順序,序號正是起到保證重組順序的作用。
序號佔用 4 位元組,32 位,它的範圍是 [0,$2^{32}$]。TCP是位元組流服務,會對每一個傳送的位元組進行編號。在建立連線的時候,系統會給定一個 ISN(初始序號),然後這個裝置在當前連線中傳送的第一個位元組的序號就是 ISN+1,假設 ISN 初始為0,那第一個位元組的序號就是 1。
舉個例子,假設ISN為0,傳送端第一次傳送 100 位元組的封包,那這第一個 TCP段的序號就是1,下次再傳送 100位元組的封包,那這第二個 TCP段的序號就是 101。
這樣一來,最大可以一直標記 $2^{32}$個位元組,也就是 4個G的資料。當達到最大值後,又會從 0 開始標記。
序號只有在下面兩種情況下才有用:
當資料傳送出去,接收方收到之後,會回覆一個確認序號回覆給傳送方,這個確認序號表示接收方希望下次接收的序號。例如傳送了序號為501的,長度為100的TCP段,那接收方收到後要回復 601的確認序號,表示【0-600】的位元組已經接收,下次希望收到第 601個位元組以後的資料。
為了提高效率,並不是每次接收到TCP段都會馬上回復給傳送方,而是採用累積確認的方式,即每傳送多個連續 TCP 段,可以只對最後一個 TCP 段進行確認。
確認序號只有在 ACK 標誌位被設定的時候才有效。
之所以需要首部長度,是因為可選項的大小是不固定的,如果沒有可選項的話,那首部長度就是 20位元組。這個標示部分佔 4 bit,單位是4位元組,4bit 可表示的最大值是 15,一個單位表示的長度是4位元組,所以首部長度最大可以是 15 x 4位元組
,也就是 60 位元組。
顧名思義,是保留位,佔用6個位元位,目前的值為 0。
協定中有 6 個位元標記位,可以理解為 TCP 段的型別。
URG
1個位元位,當被設定為1時,表明緊急指標欄位有效,該報文段有緊急資料,應儘快傳送。
ACK
當 ACK 設定為1時,確認號才有效,連線建立後,所有的報文段ACK都為 1。
PSH
當 PSH 設定為1時,接收方應該儘快將這個報文段交給應用層,而不再等待整個快取填滿再交付。
RST
當 RST 為1時,表示連線出現嚴重錯誤,必須重新建立連線。
SYN
在建立連線時用到。
當SYN=1,ACK=0時,表明這是一個連線請求報文段。
當SYN=1,ACK=1時,表明對方同意連線。
FIN
用來釋放一個連線視窗。當FIN=1時,表明此報文段的傳送方不再傳送資料,請求釋放單向連線。TCP斷開連線用到。
大小為2個位元組,表示傳送方自己的接收視窗,用來告訴對方允許傳送的資料量,最大為65535位元組。
校驗和是必需的,是一個端到端的校驗和,由傳送端計算,然後由接收端驗證。其目的是為了發現TCP首部和資料在傳送端到接收端之間發生的任何改動。如果接收方檢測到校驗和有差錯,則TCP段會被直接丟棄。
佔2位元組,當URG=1時,緊急指標表示本報文段中的緊急資料的位元組數,表示從這個 TCP段的序號開始的後的若干個位元組是緊急資料,之後的就是普通資料。
假設此TCP段的序號為101,緊急指標為30,那就表示從 101開始,直到 131,【101,131】這個區間內為緊急資料。
資料要完成傳輸,必須要建立連線。由於建立TCP連線的過程需要來回3次,所以,將這個過程形象的叫做三次握手。
而連線斷開的時候要經過四次資料傳輸,所以也被稱為4次揮手。
啥都別說了,先看圖吧。
結合上面的圖來看更清楚。
先說三次握手吧,連線是後續資料傳輸的基礎。就像我們打電話一樣,必須保證我和對方都拿著電話在聽,才能保證我們兩個說的話對方能夠接收到。
三次握手大概就是這個意思:
張三想跟李四聊聊天,於是張三撥通了李四的手機號,李四聽到鈴聲響起,按了接聽按鈕。
張三:Hi,李四,是你嗎?嘮兩塊錢的呀!
李四:Hi,張三,是我,可以嘮。
張三:好,我確定是你了,接下來我要開始和你嘮了。
看上去多少有點兒死板,但程式上確實就是這樣的。
1、第一次握手
首先使用者端發起連線請求,向伺服器傳送 TCP段,段中包含了目標埠和本機埠,設定 SYN 標誌位為1,序號為 x,也就是初始序號 ISN,如果是第一個連線,很有可能就是 0。當然,此時伺服器對應的埠要處於監聽狀態。此時,使用者端進入 SYNC_SENT 狀態,等待伺服器的確認。
2、第二次握手
伺服器端收到使用者端發來的 SYN 段,對這個SYN報文段進行確認,設定Acknowledgment Number為x+1(Sequence Number+1),這就是確認序號。同時,伺服器端還要傳送 SYN 請求資訊,將SYN位置為1,Sequence Number為 y(伺服器端的TCP段序號)。伺服器端將上述所有資訊放到一個TCP段(即SYN+ACK段)中,一併行送給使用者端,此時伺服器進入SYN_RECV狀態。
3、第三次握手
使用者端接收到伺服器端發來的 SYN+ACK 段後,傳送一個 ACK 給伺服器端,將 Acknowledgment Number 設定為 y+1。此時使用者端進入 ESTABLISHED(已連線)狀態,伺服器端接收到此 TCP段,也將進入 ESTABLISHED 狀態,也就標誌著三次握手結束,連線成功建立。
三次握手完成之後,連線就建立了,之後就可以愉快的傳輸資料了。
一旦有了感情(連線),再分手就難了,難到需要四次揮手。不像 UDP 那樣,沒有連線,說分就分。
當用戶端和伺服器端雙方傳送資料完成後,一般會由使用者端主動發起斷開連線的請求,當然,也有少數情況是伺服器端主動發起。
以最常見的使用者端發起斷開連線為例,說一下四次揮手的過程。
1、第一次揮手
使用者端設定序號(Sequence Number)和確認序號(Acknowledgment Number),傳送一個 FIN 段給伺服器。這時,使用者端進入 FIN_WAIT_1狀態,意味著使用者端沒有資料要傳送了。
2、第二次揮手
伺服器端收到 FIN 報文段,向用戶端傳送一個 ACK 段,使用者端進入 FIN_WAIT_2 狀態。表示伺服器端已同意連線關閉請求。
3、第三次揮手
伺服器端向用戶端傳送 FIN 段,請求關閉連線,同時伺服器端進入 LAST_ACK 狀態。
4、第四次揮手
使用者端收到伺服器端發來的 FIN 段,向伺服器端傳送 ACK 段,之後使用者端進入TIME_WAIT狀態。伺服器端收到使用者端的ACK 段以後,就關閉連線。
上面就是由使用者端主動發起關閉連線的過程。
TCP 是一個全雙工的位元組流服務,意思就是說兩個端點都可以同時傳送和接收訊息。
正常情況下需要四次揮手才能完成連線的完全斷開。但是有一種情況是這樣的,只主動關閉自己到對方的連線,但是對方還是可以給自己傳送資料。
Wireshark 是幫助我們分析網路請求的利器,建議每個同學都裝一個。我們先用 Wireshark 抓取一個完整的連線建立、傳送資料、斷開連線的過程。
我這兒只簡單的介紹一下操作流程。
1、首先開啟 Wireshark,在歡迎介面會列出當前機器上的所有網口、虛機網口等可以抓取的部件。
2、我接下來要用 Telnet 連線一個外網伺服器,所以我選擇第一個 WI-FI:en0,這樣 Wireshark 就會捕獲我連線的 wifi 上的網路傳輸。
3、我只想要抓一下最簡單的 TCP 連線、發資料、斷開的過程,所以要做一下抓取過濾。Wireshark 中的過濾器可以實現這樣的需求。在下圖紅框部分可以選了一個過濾器。
4、因為當前沒有直接可用的符合要求的過濾器,所以,需要自己寫一個。點選前面的綠色書籤圖示,然後在彈出視窗中點選加號新增一個。
內容如下,語法就不解釋了,一看就知道。
tcp and host 你的遠端ip
5、選擇好剛新增的這個過濾器,雙擊wifi這個 interface 進入就開始捕獲了。
6、我用 telnet 連線這臺伺服器的 6379 埠 telnet ip 6379
,因為這臺伺服器上裝著 redis,可以模擬發資料。
在控制檯中連線到 6379 埠成功,然後在 Wireshark 上馬上捕獲到了。
這就是三次握手的過程。
7、然後直接關掉終端,這樣會自動觸發斷開連線,並且傳送最少的資料,方便我們觀察。整個的過程都被 Wireshark 完整的捕捉到了。
第一部分是連線建立的三次握手,第二部分是發了長度為 1個位元組的資料,第三步是使用者端主動發起的斷開連線的四次揮手過程。
有圖先看圖
概覽資訊
也就是圖中最上面的紅色框部分。這一次的連線建立和中斷一共產生了來回 8 次的請求,每次請求會在列表上列出時間、源端IP、目的端IP、乙太網幀長度以及概覽資訊,包括資料傳輸方向(源埠->目標埠)、標記情況、序號、確認序號、視窗大小等等。
乙太網幀
在每次請求資訊中,還包括乙太網幀,因為資訊最終都會通過幀的形式傳送出去。
IP資料包
還有 IP 資料包內容,其中包含了源端 IP 和 目的端 IP 等資訊。
TCP段
TCP 段當然是重點了,其中包含了 TCP 協定中的所有資訊,包括埠號、
MTU是什麼
MTU 全稱是最大傳輸單元,一個在網路上傳輸的包不能無限大,MTU 一般是對於鏈路層而言的,拿乙太網來說,在鏈路層允許傳送的最大的乙太網幀的資料部分就是 1500位元組。注意是乙太網幀的資料部分,再加上乙太網幀的頭部,會大於1500位元組。
通過 ifconfig
(windows 系統是 ipconfig)可檢視本機各個網路介面(網路卡)的MTU 大小。
MSS是什麼
MSS 指TCP最大報文長度,是TCP協定定義的一個選項,MSS選項用於在TCP連線建立時,收發雙方協商通訊時每一個報文段所能承載的最巨量資料長度。還是用乙太網為例,MTU是 1500位元組,減去TCP頭(20位元組)和IP頭(20位元組),就是MSS 1460位元組。
粘包就是將幾個比較小的 TCP 包合併成一個包,這樣就只傳送一次就可以將多個小包傳送出去。例如下面這樣,一個TCP報文請求中,包含小包A、B、C,每一個小包原本都是一個TCP報文。
為什麼要粘包呢?一個一個傳送不行嗎?
其實是可以的,只不過在多數情況下來一個包馬上就傳送可能會造成網路擁塞,一個TCP 報文傳輸到鏈路層的時候,會加上TCP頭和IP頭,佔用40位元組,如果傳送的資料內容很小,比如只有1個位元組,為了這一位元組的內容,要有40倍的額外的資訊被傳輸,是不是有點浪費。
為了減少這種浪費,TCP 協定就做了一些優化,比如 Nagle 演演算法:
由於現在的寬頻和裝置效能的提升,Nagle 演演算法其實可以關閉了,有些裝置上預設就是關閉的,也可以在寫 Socket 的程式碼的時候主動關閉掉,關閉之後呢,只要接收端處理能力夠快,可以保證來一個包馬上傳送,對那些要求實時反饋的應用來說尤其重要。
那來一個包發一個包,是不是就不會有粘包的問題了?也不是,這就要看接收端的處理能力了,接收端會有一個接收緩衝區,來不及被應用程式處理的會暫時放到這裡,如果應用程式處理能力較差,這裡還是會出現粘包。
既然發生了粘包,就要把這些大包拆成小包。怎麼拆分其實都是上層應用的事兒了,核心要點就是約定好分隔符。舉個簡單的例子,比如說將包A和包B用一個特殊字元 $
分隔開,那應用在拆包的時候就要根據這個特殊字元進行分隔。當然了,真實情況要比這個複雜的多,如果你用過 Netty,就會發現 Netty 提供了多種處理粘包拆包的方式。
粘包是為了將多個小包變成一個大包,而半包是把超大包拆成小包。比如下圖,假設包B是一個很大的包,已經超過了MSS 了,單單傳送它自己都發不過去了,所以只能將它拆開,一部分一部分的傳送。
半包就沒那麼複雜了,純粹是因為單獨的包太大,協定不支援這麼大的包,只能拆開。
這樣一部分一部分的包,到了接收端之後就要將其合併為一個整體,合併也比較簡單,就是如果這個部分包沒有開始或沒有結束標誌,就表示它不是完整的,需要給其找到對應的其他部分。
接收方通告的視窗稱為 offered window,意思就是說我這邊可以接受的最大位元組數為這麼多。例如下圖中的紅框部分為 offered window, 大小為 6 位元組,傳送端最大一次只能傳送 6 個位元組,要不然接收方就沒有能力接收了。
可用視窗 = offered window - 已經傳送但未被確認的位元組大小,這個值由傳送方自己計算。前面說了三次握手,傳送方發出去包,接收方接到後會反回一個 ACK,發出去但未收到ACK的資料也會佔用視窗,表明接收方正在處理,所以,可用視窗的大小是 offered window 減去未收到 ACK 的大小。
為什麼叫滑動視窗呢,看上面的圖,把一個個位元組想象成排成一排的格子。
首先看時刻1:紅色格子的部分就是offered window,大小為6位元組,後面10、11、12位元組因為沒在視窗內,所以不能傳送。已傳送但未被確認的也佔用視窗大小,所以最終可用視窗就是 7、8、9這三個位元組。
再看時刻2:剛才未被確認的 4、5、6位元組收到了 ACK,所以1-6都變成了過去式,然後視窗覆蓋到了7、8、9、10、11、12 這6個位元組,對比時刻1和時刻2,給我們的感覺就是視窗(紅色格子)向右滑動了,這就是所謂的滑動視窗了。
還有,視窗兩個邊沿的相對運動增加或減少了視窗的大小。
當視窗左邊沿向右邊沿靠近為視窗合攏。這種現象發生在資料被傳送和確認時,假設現在接收方處理資料的時間變長了,來不及快速處理,那接收方在下次ACK的時候返回的視窗大小可能就會變小。
當視窗右邊沿向右移動時將允許傳送更多的資料, 我們稱之為視窗張開。 這種現象發 生在另一端的接收程序讀取已經確認的資料並釋放了 TCP的接收快取時。
在使用 TCP 傳輸的過程中,肯定是希望資料傳送的越快越好,但是在實際使用場景中,由於傳送端和接收端處理資料的速度不一致,或者由於中間路由器效能限制、頻寬限制等原因,傳送的速度越快,越有可能導致丟包的情況。比如一下子傳送了10M的資料出來,但是中間路由器只能處理 5 M,很可能就會把一些包丟棄。
因而設計了慢啟動和擁塞避免演演算法,這兩個設計都是為了合理的匹配發端的傳送速度與收端的處理速度。
在連線剛建立的時候,傳送端也不知道應該按什麼速度發比較合適,所以就採用了一種漸進式的方式,就是慢啟動的方式。
前面說了 offered window 是接收端的,在傳送端也有一個視窗,叫做擁塞視窗,記做 cwnd,擁塞視窗初始化為 1 ,表示 1個報文段,也就是允許傳送1個報文段,之後每當每當收到接收端返回的 ACK 時,就將 cwnd 的值加1。第一次傳送一個資料包,當收到 ACK 後,cwnd 變為2,然後下一次傳送兩個資料包,當收到這兩個資料包的 ACK 時,cwnd 就變成 4 。以此類推,這個增長是呈指數級的。
但是,在這個過程中,也是有限制的,傳送的資料包大小要在訊息接收端返回的通告視窗大小和 cwnd 中取較小的那個值。假設一個報文大小為 1024 位元組,當 cwnd 為2,通告視窗大小為 4096 位元組時,那傳送端你可以連著傳送2個資料包,也就是取 cwnd 的值,當 cwnd 為8 時,通告視窗大小仍然為 4096 位元組時,那傳送端最多可連續傳送 4 個資料包,也就是不能超過 4096 位元組。
擁塞避免演演算法其實和慢啟動是在一起使用的。在慢啟動中除了有擁塞視窗外, 還有一個叫做啟動門限(ssthresh)的引數。啟動門限預設的是 65535 位元組。
在慢啟動中,cwnd 是呈指數級增長,但是這個增長速度太快了,所以,擁塞避免演演算法就是讓這個增速減緩的方式。
當 cwnd < ssthresh 的時候,就使用慢啟動。
當 cwnd > ssthresh 的時候,就啟動擁塞避免演演算法。
擁塞避免演演算法保證當 cwnd 超過限制之後,每次收到一個確認時將 cwnd 增加 1/cwnd。
當擁塞發生時(超時或收到重複確認),ssthresh 被設定為當前視窗大小的一半(cwnd和接收方通告視窗大小的最小值,但最少為 2個報文段)。
用一張圖來說明慢啟動和擁塞避免演演算法
假定當 cwnd 為32個報文段時就會發生擁塞。於是設定 ssthresh 為1 6個報文段, 而 cwnd 為1個報文段。 在時刻 0傳送了一個報文段, 並假定在時刻 1接收到它的 ACK,此時 cwnd 增加為2。接著傳送了2個報文段,並假定在時刻 2接收到它們的 ACK,於是 cwnd 增加為4 (對每個 ACK 增加1次)。這種指數增加演演算法一直進行到在時刻 3和4之間收到8個A C K後 cwnd 等 於 ssthresh 時才停止,從該時刻起,cwnd 以線性方式增加,在每個往返時間內最多增加 1個報 文段。
正如我們在這個圖中看到的那樣, 術語「慢啟動」並不完全正確。 它只是採用了比引起 擁塞更慢些的分組傳輸速率, 但在慢啟動期間進入網路的分組數增加的速率仍然是在增加的。 只有在達到 ssthresh 擁塞避免演演算法起作用時,這種增加的速率才會慢下來。
什麼情況下要重傳,當傳送端認為丟包了就要重傳,有兩種情況下傳送端就認為丟包了,於是就會發起重傳。
傳送端在一段時間(超時時間)後沒有收到傳送端返回的 ACK ,就認為這個包丟了,這個超時時間並不是固定的。
這裡面有兩個概念,RTT 和 RTO。
RTT(Round Trip Time):往返時延,也就是封包從發出去到收到對應 ACK 的時間。RTT 是針對連線的,每一個連線都有各自獨立的 RTT。
RTO(Retransmission Time Out):重傳超時,也就是前面說的超時時間。
接收端回覆的 ACK 會帶著包的序號,當接收端重複三次收到同一個序號的ACK時,就要重傳這個包;
比如下面圖中畫的這樣:
1、seq=1的包發過去,接收端ACK=2,表示期望下次出現的序號為2,然後傳送端就發了 seq=2的包,接收端接到後回覆 ACK=3,表示期望下次收到序號為3的包,這是傳送端第一次收到 ACK=3;
2、傳送端繼續傳送 seq=3 的包,但是這個包可能傳輸的比較慢(比如路由選擇的不好),接收端一直沒收到;
3、傳送端先不管,繼續傳送 seq=4 的包,接收端收到後,回覆ACK,正常情況下應該是 ACK=5,但是序號為3的包還沒收到,所以再次回覆ACK=3,這是第二次收到ACK=3;
4、傳送端繼續不管,接著傳送 seq=5的包,接收端收到後,回覆ACK,正常情況下應該是 ACK=6,但是序號為3的包還沒收到,所以再次回覆ACK=3,這是第三次收到ACK=3;
到目前為止,已經收到三次 ACK=3了,然後傳送端就重新傳送 seq=3的包,這時候就當做這個包已經丟了。這就是快速重傳。
如果覺得還不錯的話,給個推薦吧!
公眾號「古時的風箏」,Java 開發者,專注 Java 及周邊生態。堅持原創乾貨輸出,你可選擇現在就關注我,或者看看歷史文章再關注也不遲。長按二維條碼關注,跟我一起變優秀!