一個網管的自我修養之TCP協議
今天,繼續來網管的自我修養之TCP協議,這可是除 IP 協議外另一個核心協議了。
TCP 協議是網絡傳輸中至關重要的一個協議,它位于傳輸層。向上支持 FTP、TELNET、SMTP、DNS、HTTP等常見的應用層協議,向下要與網絡層的 IP 協議相互配合,實現可靠的網絡傳輸。
分層網絡模型
OSI 7層模型
為了讓全世界的計算機有效的互聯起來,國際標準化組織提出了一種概念化的網絡模型,開放式系統互聯模型(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 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 協議
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 段的負載。
TCP 段的大小也是有限制的,最大是 1460 字節,這是怎么算出的呢?
最終由網卡發出去的數據包叫做以太網幀,以太網幀由以太網首部和負載構成。
以太網幀的負載就是一個 IP 數據報,IP數據報由IP首部和負載構成。
IP數據報的負載就是一個 TCP段。所以,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,]。TCP是字節流服務,會對每一個發送的字節進行編號。在建立連接的時候,系統會給定一個 ISN(初始序號),然后這個設備在當前連接中發送的第一個字節的序號就是 ISN+1,假設 ISN 初始為0,那第一個字節的序號就是 1。
舉個例子,假設ISN為0,發送端第一次發送 100 字節的數據包,那這第一個 TCP段的序號就是1,下次再發送 100字節的數據包,那這第二個 TCP段的序號就是 101。
這樣一來,最大可以一直標記 個字節,也就是 4個G的數據。當達到最大值后,又會從 0 開始標記。
序號只有在下面兩種情況下才有用:
- 數據字段至少包含一個字節。
- 是一個 SYN 段,或者是 FIN 段,或者是 RST 段。
確認序號
當數據發送出去,接收方收到之后,會回復一個確認序號回復給發送方,這個確認序號表示接收方希望下次接收的序號。例如發送了序號為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個標志位
協議中有 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 抓住 TCP
Wireshark 是幫助我們分析網絡請求的利器,建議每個同學都裝一個。我們先用 Wireshark 抓取一個完整的連接建立、發送數據、斷開連接的過程。
我這兒只簡單的介紹一下操作流程。
1、首先打開 Wireshark,在歡迎界面會列出當前機器上的所有網口、虛機網口等可以抓取的部件。
2、我接下來要用 Telnet 連接一個外網服務器,所以我選擇第一個 WI-FI:en0,這樣 Wireshark 就會捕獲我連接的 wifi 上的網絡傳輸。
3、我只想要抓一下最簡單的 TCP 連接、發數據、斷開的過程,所以要做一下抓取過濾。Wireshark 中的過濾器可以實現這樣的需求。在下圖紅框部分可以選了一個過濾器。
4、因為當前沒有直接可用的符合要求的過濾器,所以,需要自己寫一個。點擊前面的綠色書簽圖標,然后在彈出窗口中點擊加號添加一個。
內容如下,語法就不解釋了,一看就知道。
5、選擇好剛添加的這個過濾器,雙擊wifi這個 interface 進入就開始捕獲了。
6、我用 telnet 連接這臺服務器的 6379 端口 telnet ip 6379,因為這臺服務器上裝著 redis,可以模擬發數據。
在控制臺中連接到 6379 端口成功,然后在 Wireshark 上馬上捕獲到了。
這就是三次握手的過程。
7、然后直接關掉終端,這樣會自動觸發斷開連接,并且發送最少的數據,方便我們觀察。整個的過程都被 Wireshark 完整的捕捉到了。
第一部分是連接建立的三次握手,第二部分是發了長度為 1個字節的數據,第三步是客戶端主動發起的斷開連接的四次揮手過程。
Wireshark 簡單介紹
有圖先看圖
概覽信息
也就是圖中最上面的紅色框部分。這一次的連接建立和中斷一共產生了來回 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 算法規定每次只有收到上一個包的確認(ACK)之后,才會發送下一個包,在這個時間段內正好將小的包粘到一起;
- 但是太多的包也不行,大小不能超過 MSS ,也就是前面剛說的 1460字節,太大了裝不下;
- 如果沒有那么多小包,也不能一直等著,有一個超時時間,大約是200ms,超過這個時間也要發送;
由于現在的寬帶和設備性能的提升,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 程序員,有時也是用 Python 、React 做一些小東西。喜歡并擅長解決問題、處理 bug。堅持原創干貨輸出,關注我,一起變優秀!
本文轉載自微信公眾號「古時的風箏」,可以通過以下二維碼關注。轉載本文請聯系公眾號。