TCP / IP 精彩回顧-必看
TCP/IP 協議出現的原因是互聯網世界各個主機作為一個個獨立的個體,如何制定統一的規則讓他們互相通訊是達成萬物互聯的紐帶。基於此,設定了 TCP/IP 協議來規範網路訪問行為。TCP/IP 並不是一個具體的協議,而是表示一系列協議的統稱,包括 IP、ICMP、TCP 以及 HTTP、FTP、POP3等等。個人主機遵循對應的協議就能與同樣遵循該協議的第三方主機進行通訊。
1. 分層模型
網路協議通常分成不同的層次進行開發,每一層負責不同的通訊功能。數據傳輸的過程就像發快遞一樣,每經過一個郵局,都加上當前郵局的印戳,告知郵差當前快遞經過了哪裡,下一個郵局又是哪。TCP/IP 通常被認為是一個四層的協議系統,由上至下為:應用層、傳輸層、網路層和鏈路層。
- 應用層:定義上層應用可以直接使用的高級協議,如HTTP、FTP等;
- 傳輸層:定義控制數據傳輸的協議,用以保證數據的可靠性和順序到達性等,如TCP、UDP協議;
- 網路層:定義不同網路類型間通訊的協議,如IP協議用於實現網際路由,ICMP 協議用於檢測網路的暢通性,ARP 協議用於獲取設備 MAC 地址等;
- 鏈路層:定義網路介質上的傳輸協議,和電氣相關,如 Ethernet 協議、802.3 協議等,主要由作業系統的網卡驅動程式實現。
說到4層模型,我們自然聯想到 OSI 的 7 層模型,有大佬總結了一個圖,我就拿過來用:
我們拿 TCP/IP 四層模型具體分析一下每一層都會做什麼事情:
1.1 鏈路層
鏈路層做的事情很簡單:把0、1按照位元組為單位進行傳輸。乙太網規定一組電訊號就是一個數據包,一個數據包被稱為一幀, 制定這個規則的協議就是乙太網協議。一個完整的乙太網數據包如下圖所示:
整個數據幀由首部、數據和尾部三部分組成,首部固定為14個位元組,包含了目標MAC地址、源MAC地址和類型;數據最短為46個位元組,最長為1500個位元組,如果需要傳輸的數據很長,就必須分割成多個幀進行發送;尾部固定為4個位元組,表示數據幀校驗序列,用於確定數據包在傳輸過程中是否損壞。因此,乙太網協議通過對電訊號進行分組並形成數據幀,然後通過物理介質把數據幀發送給接收方。那麼乙太網如何來識接收方的身份呢?
乙太網規協議定,接入網路的設備都必須安裝網路適配器,即網卡, 數據包必須是從一塊網卡傳送到另一塊網卡。而網卡地址就是數據包的發送地址和接收地址,也就是幀首部所包含的MAC地址,MAC地址是每塊網卡的身份標識,就如同我們身份證上的身份證號碼,具有全球唯一性。MAC地址採用十六進位標識,共6個位元組, 前三個位元組是廠商編號,後三個位元組是網卡流水號,例如 5D-3E-11-3F-05-E1。
有了MAC地址以後,乙太網採用廣播形式,把數據包發給該子網內所有主機,子網內每台主機在接收到這個包以後,都會讀取首部數據中的目標MAC地址,然後和自己的MAC地址進行對比,如果相同就做下一步處理,如果不同,就丟棄這個包。
所以鏈路層的主要工作就是對電訊號進行分組並形成具有特定意義的數據幀,然後以廣播的形式通過物理介質發送給接收方。
1.2 網路層
鏈路層提到定位唯一一個設備的方式是查詢網卡地址。那麼在網路層要做的事情是:根據IP找到IP對應的設備。
我們可以思考一下:
IP是如何和MAC地址對應上的呢?網路層主要提供以下協議進行傳輸和定位。
IP協議
網路通訊是基於IP協議來進行,MAC地址不具備規律性所以無法用來作為通訊協議,但是它是唯一的所以可以用來確定一台設備。
IP地址目前有兩個版本,分別是IPv4和IPv6,IPv4是一個32位的地址,常採用4個十進位數字表示。IP協議將這個32位的地址分為兩部分,前面部分代表網路地址,後面部分表示該主機在區域網中的地址。由於各類地址的分法不盡相同,以C類地址 192.168.24.1 為例,其中前24位就是網路地址,後8位就是主機地址。因此, 如果兩個IP地址在同一個子網內,則網路地址一定相同。為了判斷IP地址中的網路地址,IP協議還引入了子網掩碼, IP地址和子網掩碼通過按位與運算後就可以得到網路地址。
由於發送者和接收者的IP地址是已知的(應用層的協議會傳入), 因此我們只要通過子網掩碼對兩個IP地址進行AND運算後就能夠判斷雙方是否在同一個子網。
IP地址的組成:
IP地址 = 網路地址 + 主機地址,比如:
IP地址分類:
最初設計互聯網路時,為了便於定址以及層次化構造網路,每個 IP 地址包括兩個標識碼(ID),即網路 ID 和主機ID。同一個物理網路上的所有主機都使用同一個網路 ID,網路上的一個主機(包括網路上工作站,伺服器和路由器等)有一個主機 ID 與其對應。Internet 委員會定義了 5 種 IP 地址類型以適合不同容量的網路,即A類~E類。
其中A、B、C 3類由 InternetNIC 在全球範圍內統一分配,D、E類為特殊地址。
IP地址分為五大類:A類、B類、C類、D類和E類,如下圖所示:
類別 | 最大網路數 | IP地址範圍 | 單個網段最大主機數 | 私有IP地址範圍 |
---|---|---|---|---|
A | 126(2^7-2) | 1.0.0.0-127.255.255.255 | 16777214 | 10.0.0.0-10.255.255.255 |
B | 16384(2^14) | 128.0.0.0-191.255.255.255 | 65534 | 172.16.0.0-172.31.255.255 |
C | 2097152(2^21) | 192.0.0.0-223.255.255.255 | 254 | 192.168.0.0-192.168.255.255 |
IP 路由選擇
當一個 IP 數據包準備好了的時候,IP數據包(或者說是路由器)是如何將數據包送到目的地的呢?它是怎麼選擇一個合適的路徑來”送貨”的呢?
最特殊的情況是目的主機和主機直連,那麼主機根本不用尋找路由,直接把數據傳遞過去就可以了。至於是怎麼直接傳遞的,這就要靠 ARP 協議了,後面會講到。
稍微一般一點的情況是,主機通過若干個路由器(router)和目的主機連接。那麼路由器就要通過 IP 包的資訊來為 IP 包尋找到一個合適的目標來進行傳遞,比如合適的主機,或者合適的路由。路由器或者主機將會用如下的方式來處理某一個 IP 數據包。
如果 IP 數據包的 TTL (生命周期)已到,則該 IP 數據包就被拋棄。
搜索路由表,優先搜索匹配主機,如果能找到和 IP 地址完全一致的目標主機,則將該包發向目標主機。
搜索路由表,如果匹配主機失敗,則匹配同子網的路由器,這需要「子網掩碼」的協助。如果找到路由器,則將該包發向路由器。
搜索路由表,如果匹配同子網路由器失敗,則匹配同網號路由器,如果找到路由器,則將該包發向路由器。
搜索路由表,如果以上都失敗了,就搜索默認路由,如果默認路由存在,則發包。
如果都失敗了,就丟掉這個包。
以上過程說明:IP 包是不可靠的,因為它不保證送達。
內網IP(內網保留地址)
Internet 設計者保留了 IPv4 地址空間的一部分供專用地址使用,專用地址空間中的 IPv4 地址叫專用地址。這些地址永遠不會被當做公用地址來分配,所以專用地址永遠不會與公用地址重複。
IPv4 專用地址如下:
IP等級 | IP段位 | 默認子網掩碼 |
---|---|---|
A 類 | 10.0.0.0 – 10.255.255.255 | 255.0.0.0 |
B 類 | 172.16.0.0 – 172.31.255.255 | 255.255.0.0 |
C 類 | 192.168.0.0 – 192.168.255.255 | 255.255.255.0 |
特殊的網址
- 每一個位元組都為 0 的地址
0.0.0.0
對應於當前主機; - IP 地址中的每一個位元組都為 1 的IP地址
255.255.255.255
是當前子網的 廣播地址; - IP 地址中凡是以
11110
開頭的E類IP地址都保留用於將來和實驗使用; - IP 地址中不能以十進位
127
作為開頭,該類地址中數字127.0.0.1
到127.255.255.255
用於迴路測試,如:127.0.0.1
可以代表本機 IP 地址,用 「//127.0.0.1」 就可以測試本機中配置的 Web 伺服器; - 網路 ID 的第一個 8 位組也不能全置為 0,全 0 表示本地網路。
ARP協議
ARP 叫做地址解析協議,是根據IP地址獲取MAC地址的一個網路層協議。其工作原理如下:
ARP首先會發起一個請求數據包,數據包的首部包含了目標主機的IP地址,然後這個數據包會在鏈路層進行再次包裝,生成乙太網數據包,最終由乙太網廣播給子網內的所有主機,每一台主機都會接收到這個數據包,並取出標頭裡的IP地址,然後和自己的IP地址進行比較,如果相同就返回自己的MAC地址,如果不同就丟棄該數據包。ARP接收返回消息,以此確定目標機的MAC地址;與此同時,ARP還會將返回的MAC地址與對應的IP地址存入本機ARP快取中並保留一定時間,下次請求時直接查詢ARP快取以節約資源。
一個典型的 ARP 快取資訊如下,在任意一個系統裡面用「 arp -a 」命令:
root: $ arp -a
? (10.32.0.253) at ee:ff:ff:ff:ff:ff [ether] on eth0
? (10.32.0.38) at ee:ff:ff:ff:ff:ff [ether] on eth0
? (10.32.0.107) at ee:ff:ff:ff:ff:ff [ether] on eth0
RARP協議
反向地址轉換協議(RARP:Reverse Address Resolution Protocol)。 反向地址轉換協議(RARP)允許區域網的物理機器從網關伺服器的 ARP 表或者快取上請求其 IP 地址。
RARP工作原理:
- 將源設備和目標設備的MAC地址欄位都設為發送者的MAC地址和IP地址,發送主機發送一個本地的RARP廣播,能夠到達網路上的所有設備,在此廣播包中,聲明自己的MAC地址並且請求任何收到此請求的RARP伺服器分配一個IP地址;
- 本地網段上的RARP伺服器收到此請求後,檢查其RARP列表,查找該MAC地址對應的IP地址;
- 如果存在,RARP伺服器就給源主機發送一個響應數據包並將此IP地址提供給對方主機使用;如果不存在,RARP伺服器對此不做任何的響應;
- 源主機收到從RARP伺服器返回的響應資訊,就利用得到的IP地址進行通訊;如果一直沒有收到RARP伺服器的響應資訊,表示初始化失敗。
ARP 和 RARP 是一對協議,對比一下:
- ARP 廣播後,只會有一個主機進行回應,就是攜帶某個IP的物理地址回應;而 RARP 由於有很多 RARP 伺服器保存的有請求主機的 IP 和物理地址對應資訊,所以只要保存有這個資訊的主機都會回應;
- ARP 請求物理地址,即廣播後想要得到的是物理地址;RARP 請求的是 IP 地址,即廣播後想要得到的是 IP 地址;
- ARP 解析直接是內核實現,RARP 的解析主要是讀寫存放 IP 和物理地址映射的磁碟文件;
- 由於每個主機的 TCP/IP 都實現了 ARP 解析,也包含了本身的物理地址和 IP 地址,所以當收到 ARP 請求包,就知道是否要回送資訊;
- 而 RARP 不同,為了給無盤主機進行系統引導,所以必須要有實現了 RARP 伺服器的主機進行解析;在一般的主機 TCP/IP 實現中並沒有實現 RARP 伺服器,由於其本省的複雜性,而且又和鏈路層的廣播有關,所以實現也不好,不實現也不好,最後由於內核一般不對磁碟文件進行讀寫,所以就沒有實現。
ICMP協議
上面說到 IP 協議並不是一個可靠的協議,它不保證數據被送達,那麼保證數據送達的工作應該由其他的模組來完成,其中一個重要的模組就是ICMP (網路控制報文)協議。
當傳送 IP 數據包發生錯誤:比如主機不可達,路由不可達等等,ICMP 協議將會把錯誤資訊封包,然後傳送回給主機。給主機一個處理錯誤的機會,這也就是為什麼說建立在 IP 層以上的協議是可能做到安全的原因。ICMP 數據包由 8bit 的錯誤類型和 8bit 的程式碼和 16bit 的校驗和組成。而前 16bit 就組成了 ICMP 所要傳遞的資訊。
ICMP 協議大致分為兩類:一種是查詢報文,一種是差錯報文。其中查詢報文有以下幾種用途:
- Ping 查詢;
- 子網掩碼查詢(用於無盤工作站在初始化自身的時候初始化子網掩碼);
- 時間戳查詢(可以用來同步時間)。
而差錯報文則產生在數據傳送發生錯誤的時候。
Route協議(路由協議)
通過 ARP 協議的工作原理可以發現,ARP 的 MAC 定址還是局限在同一個子網中,因此網路層引入了路由協議,首先通過 IP 協議來判斷兩台主機是否在同一個子網中,如果在同一個子網,就通過 ARP 協議查詢對應的 MAC 地址,然後以廣播的形式向該子網內的主機發送數據包;如果不在同一個子網,乙太網會將該數據包轉發給本子網的網關進行路由。網關是互聯網上子網與子網之間的橋樑,所以網關會進行多次轉發,最終將該數據包轉發到目標 IP 所在的子網中,然後再通過 ARP 獲取目標機 MAC,最終也是通過廣播形式將數據包發送給接收方。
而完成這個路由協議的物理設備就是路由器,在錯綜複雜的網路世界裡,路由器扮演者交通樞紐的角色,它會根據信道情況,選擇並設定路由,以最佳路徑來轉發數據包。
IP數據包
在網路層被包裝的數據包就叫IP數據包,IPv4數據包的結構如下圖所示:
IP 數據包由 首部 和 數據 兩部分組成,首部長度為 20 個位元組,主要包含了目標 IP 地址和源 IP 地址,目標 IP 地址是網關路由的線索和依據;數據部分的最大長度為 65515 位元組,理論上一個 IP 數據包的總長度可以達到 65535個位元組,而乙太網數據包的最大長度是 1500 個位元組,如果超過這個大小,就需要對IP數據包進行分割,分成多幀發送。
所以,網路層的主要工作是定義網路地址,區分網段,子網內 MAC 定址,對於不同子網的數據包進行路由。
1.3 傳輸層
鏈路層定義了主機的身份,即 MAC 地址, 而網路層定義了 IP 地址,明確了主機所在的網段,有了這兩個地址,數據包就從可以從一個主機發送到另一台主機。但實際上數據包是從一個主機的某個應用程式發出,然後由對方主機的應用程式接收。而每台電腦都有可能同時運行著很多個應用程式,所以當數據包被發送到主機上以後,是無法確定哪個應用程式要接收這個包。
因此傳輸層引入了 UDP協議 來解決這個問題,為了給每個應用程式標識身份,UDP 協議定義了埠,同一個主機上的每個應用程式都需要指定唯一的埠號,並且規定網路中傳輸的數據包必須加上埠資訊。 這樣,當數據包到達主機以後,就可以根據埠號找到對應的應用程式了。UDP 定義的數據包就叫做 UDP 數據包,結構如下所示:
UDP 數據包由首部和數據兩部分組成,首部長度為 8 個位元組,主要包括源埠和目標埠;數據最大為 65527 個位元組,整個數據包的長度最大可達到 65535 個位元組。
UDP 協議比較簡單,實現容易,但它沒有確認機制, 數據包一旦發出,無法知道對方是否收到,因此可靠性較差,為了解決這個問題,提高網路可靠性,TCP 協議 就誕生了,TCP 即傳輸控制協議,是一種面向連接的、可靠的、基於位元組流的通訊協議。簡單來說 TCP 就是有確認機制的 UDP 協議,每發出一個數據包都要求確認,如果有一個數據包丟失,就收不到確認,發送方就必須重發這個數據包。
為了保證傳輸的可靠性,TCP 協議在 UDP 基礎之上建立了三次對話的確認機制,經過三次對話之後,主機A才會向主機B發送正式數據,而 UDP 是面向非連接的協議,它不與對方建立連接,而是直接就把數據包發過去了。所以 TCP 能夠保證數據包在傳輸過程中不被丟失,但美好的事物必然是要付出代價的,相比 UDP,TCP 實現過程複雜,消耗連接資源多,傳輸速度慢。
TCP 數據包和 UDP 一樣,都是由首部和數據兩部分組成,唯一不同的是,TCP 數據包沒有長度限制,理論上可以無限長,但是為了保證網路的效率,通常 TCP 數據包的長度不會超過IP數據包的長度,以確保單個 TCP 數據包不必再分割。
總結一下,傳輸層的主要工作是定義埠,標識應用程式身份,實現埠到埠的通訊,TCP 協議可以保證數據傳輸的可靠性。
1.4 應用層
理論上講以上三層協議已經可以完全支援數據從一台主機傳輸到另一台主機。但是考慮到數據傳輸的安全性,數據流粘包拆包相關,數據格式規範化等等問題,在應用層定義了各種各樣的協議來規範數據傳輸的標準和基礎的校驗,常見的有 HTTP、FTP、SMTP 等,通過這些規範化協議約定數據傳輸雙方的行為。
2. TCP / UDP詳解
2.1 TCP 協議
TCP 是面向連接的傳輸層協議。
每一條 TCP 連接只能是點對點的(一對一),提供可靠交付的服務。
TCP 面向位元組流,提供全雙工通訊。
下面我們會對 TCP 的這些特性一一講解。
2.1.1 TCP 首部格式
TCP 首部數據通常包含 20 個位元組(不包括任選欄位):
- 第 1-2 兩個位元組:源埠號;
- 第 3-4 兩個位元組:目的埠號;
- 第 5-8 四個位元組:32 位序號。TCP 提供全雙工服務,兩端都有各自的序號。編號:解決網路包亂序的問題;基於時鐘生成一個序號,每4微秒加一,到 `2^32- 時又從 0 開始;
- 第 9-12 四個位元組:32 位確認序列號。上次成功收到數據位元組序號加1,ack為1才有效。確認號:解決丟包的問題;
- 第 13-14 位位元組:前 4bit 為首部長度,包括TCP頭大小,指示何處數據開始;後面 6bit 為保留位,這些位必須是 0,為了將來定義新的用途而保留;最後面 6bit 為標誌域,表示為:緊急標誌、有意義的應答標誌、推、重置連接標誌、同步序列號標誌、完成發送數據標誌。按照順序排列是:URG、ACK、PSH、RST、SYN、FIN;
- 第 15-16 兩個位元組:窗口大小,接收端期望接收的位元組數。窗口大小是一個16位元組欄位,因而窗口大小最大為65535位元組;
- 第 17-18 兩個位元組:校驗和。由發送端計算和存儲,由接收端校驗;
- 第 19-20 兩個位元組:緊急指針。指向後面是優先數據的位元組,在URG標誌設置了時才有效。如果URG標誌沒有被設置,緊急域作為填充。加快處理標示為緊急的數據段;
- Options:這個是額外的功能,提供包括安全處理機制、路由紀錄、時間戳記、 嚴格與寬鬆之來源路由等;
- Padding:由於 Options 的內容不一定有多大,但是我們知道 IP 每個數據都必須要是 32 bits, 所以,若 Options 的數據不足 32 bits 時,則由 padding 主動補齊;
- Data :該 TCP 協議包負載的數據。
2.1.2 TCP 中常見的位碼錶示
位碼即 TCP 標誌位,有6種標示:
-
SYN:synchronous 建立連接,連接建立時用於同步序號。當 SYN=1,ACK=0 時表示:這是一個連接請求報文段。若同意連接,則在響應報文段中使得 SYN=1,ACK=1。因此,SYN=1表示這是一個連接請求,或連接接受報文。SYN 這個標誌位只有在 TCP 建立連接時才會被置1,握手完成後 SYN 標誌位被置0;
-
ACK:acknowledgement 確認,佔1位,僅當ACK=1時,確認號欄位才有效,ACK=0時,確認號無效;
-
PSH:push 傳送,提示接收端立即從緩衝區把數據取走;
-
FIN:用來釋放一個連接。FIN=1表示:此報文段的發送方的數據已經發送完畢,並要求釋放運輸連接;
-
RST:reset 重置,要求重新建立連接。
-
RST=1:複位,表示當前連接存在問題需要重新建立連接,有時也用來拒絕非法報文段或拒絕打開一個連接。當發送RST包關閉連接時,無需通過四次揮手,可以直接關閉。而由於這並不是 TCP 連接中必須的一部分,因此不需要將ACK置1(對應上邊連接建立後,ACK必須置1),但可以置1。
-
這是個很危險的欄位,可能被用來實現RST攻擊:
假設有一個合法用戶
1.1.1.2
已經同伺服器建立了正常的連接,攻擊者構造攻擊的 TCP 數據,偽裝自己的IP為1.1.1.2
,並向伺服器發送一個帶有 RST 位的 TCP 數據段。伺服器接收到這樣的數據後,認為從1.1.1.2
發送的連接有錯誤,就會清空緩衝區中建立好的連接。這時,如果合法用戶1.1.1.2
再發送合法數據,伺服器就已經沒有這樣的連接了,該用戶就必須重新開始建立連接。 因此在面試中經常會問產生 RST 包的各種原因:- 伺服器埠未打開而客戶端來連接。很常見,特別是伺服器程式 core dump 之後重啟之前連續出現 RST 的情況會經常發生。但是某些 OS 的原因,可能不會出現這種狀況,如向一台 WIN 7 主機發送一個連接不存在的埠的請求,這台主機就不會響應:
- 連接超時。例如11, 12 兩台主機,11 主機用 setsockopt 的
SO_RCVTIMEO
選項設置了 recv 的超時時間,然後 11 向 12 發送 SYN 包請求連接,12 回應了一個 SYN 表示可以連接,但是回應太慢,超過了那個 recv 時間限制,所以 11 就直接又發了個 RST 包,又拒絕連接了; - 提前關閉。伺服器就是想要儘快關閉連接,就可以發送一個 RST;
- 收到一個不存在的連接上的報文。如 TCP 收到一個報文,但是根據它的端點套接字,找不到一個符合要求的套接字對(表示一條連接,前文講的),那說明此連接有錯了,就發送一個 RST 包。
-
-
URG:urgent 緊急,緊急指針是否有效,為1表示某一位需要優先處理;
-
Sequence number:順序號碼,占 4 個位元組,用來標記數據段的順序,TCP 把連接中發送的所有數據位元組都編上一個序號,第一個位元組的編號由本地隨機產生。給位元組編上序號後,就給每一個報文段指派一個序號,序列號seq就是這個報文段中的第一個位元組的數據編號;
-
Acknowledge number:確認號碼,占 4 個位元組,期待收到對方下一個報文段的第一個數據位元組的序號,序列號表示報文段攜帶數據的第一個位元組的編號,而確認號指的是期望接收到下一個位元組的編號,因此當前報文段最後一個位元組的編號+1即為確認號。
2.1.3 TCP 建立連接為什麼需要3次握手,關閉連接需要4次揮手
很多同學在回答這個問題:TCP 是如何保證請求的安全性,思考良久後給出的答案是通過3次握手和4次揮手來保證。這個問題給出這個答案說明很多同學對 TCP 協議只停留在簡單面試的層面上。
建立連接的握手過程只是保證信道可用,信道可用的前提下才能談請求的安全與否;關閉連接4次揮手是為了確保關閉之前數據傳輸完畢,以及確定關閉的是要關閉的連接。這些跟連接的安全性並無直接關係。
3次握手
- 第一次握手:客戶端將標誌位 SYN=1,隨機產生一個值 seq=x,並將該數據包發送給伺服器端,客戶端進入SYN_SENT 狀態,等待伺服器端確認。
- 第二次握手:伺服器端收到數據包後由標誌位 SYN=1 知道客戶端請求建立連接,伺服器端將標誌位 SYN 和 ACK 都置為 1,ack=x+1,隨機產生一個值 seq=y,並將該數據包發送給客戶端以確認連接請求,伺服器端進入SYN_RCVD 狀態。
- 第三次握手:客戶端收到確認後,檢查ack是否為 x+1,ACK 是否為 1,如果正確則將標誌位 ACK 置為 1, ack = y + 1,並將該數據包發送給伺服器端,伺服器端檢查 ack 是否為 y+1,ACK 是否為 1,如果正確則連接建立成功,客戶端和伺服器端進入 ESTABLISHED 狀態,完成三次握手,隨後客戶端與伺服器端之間可以開始傳輸數據了。
4次揮手
假設關閉連接請求是由客戶端發起的:
- 第一次揮手:客戶端發送 FIN=1,seq=x 的包給服務端,表示自己沒有數據要進行傳輸,單面連接傳輸要關閉。發送完後,客戶端進入 FIN_WAIT_1 狀態。
- 第二次揮手:服務端收到請求包後,發回 ACK=1,ack=x+1 的確認包,表示確認斷開連接。服務端進入 CLOSE_WAIT 狀態。客戶端收到該包後,進入 FIN_WAIT_2 狀態。此時客戶端到服務端的數據連接已斷開。
- 第三次揮手:服務端發送 FIN=1,seq=w 的包給客戶端,表示自己沒有數據要給客戶端了。發送完後進入 LAST_ACK 狀態,等待客戶端的確認包。
- 第四次揮手:客戶端收到 FIN=N 報文後,就知道可以關閉連接了,但是他還是不相信網路,怕伺服器端不知道要關閉,所以發送 ack=w+1 後進入 TIME_WAIT 狀態,如果服務端沒有收到 ACK 則可以重傳。伺服器端收到 ACK 後,就知道可以斷開連接了。客戶端等待了 2MSL 後依然沒有收到回復,則證明伺服器端已正常關閉,那好,我客戶端也可以關閉連接了。最終完成了四次握手。
上圖中有幾個狀態位有幾個需要解釋一下:
FIN_WAIT_1:FIN_WAIT_1 和 FIN_WAIT_2 狀態的真正含義都是表示等待對方的 FIN 報文。而這兩種狀態的區別是:FIN_WAIT_1 狀態實際上是當 SOCKET 在 ESTABLISHED 狀態時,它想主動關閉連接,向對方發送了 FIN 報文,此時該 SOCKET 即進入到 FIN_WAIT_1 狀態。而當對方回應 ACK 報文後,則進入到 FIN_WAIT_2 狀態,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應 ACK 報文,所以 FIN_WAIT_1 狀態一般是比較難見到的,而 FIN_WAIT_2 狀態還有時常常可以用 netstat
看到。
TIME_WAIT: 表示收到了對方的 FIN 報文,並發送出了ACK 報文,就等 2MSL 後即可回到 CLOSED 可用狀態了。如果 FIN_WAIT_1 狀態下,收到了對方同時帶 FIN 標誌和 ACK 標誌的報文時,可以直接進入到 TIME_WAIT 狀態,而無須經過 FIN_WAIT_2 狀態。
MSL(最大分段生存期):指 TCP 報文在 Internet 上最長生存時間,每個具體的 TCP 實現都必須選擇一個確定的 MSL 值。RFC 1122 建議是 2 分鐘,但 BSD 傳統實現採用了 30 秒。TIME_WAIT 狀態最大保持時間是 2 * MSL,也就是 1-4 分鐘。
1、 為什麼建立連接協議是三次握手,而關閉連接卻是四次握手呢?
這是因為服務端的 LISTEN 狀態下的 SOCKET 當收到 SYN 報文的建連請求後,它可以把 ACK 和 SYN ( ACK 起應答作用,而 SYN 起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的 FIN 報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可能未必會馬上會關閉 SOCKET,也即你可能還需要發送一些數據給對方之後,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這裡的 ACK 報文和 FIN 報文多數情況下都是分開發送的。
2. 為什麼不能用兩次握手進行連接?
我們知道,3次握手完成兩個重要的功能,既要雙方做好發送數據的準備工作(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被發送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作為例子,考慮電腦 A 和 B 之間的通訊,假定 B 給A 發送一個連接請求分組,A 收到了這個分組,並發送了確認應答分組。按照兩次握手的協定,A 認為連接已經成功地建立了,可以開始發送數據分組。可是,B 在 A 的應答分組在傳輸中被丟失的情況下,將不知道 A 是否已準備好,不知道 A 建立什麼樣的序列號,B 甚至懷疑 A 是否收到自己的連接請求分組。在這種情況下,B 認為連接還未建立成功,將忽略 A 發來的任何數據分組,只等待連接確認應答分組。而 A 在發出的數據分組超時後,重複發送同樣的數據分組。這樣就形成了死鎖。
3. 為什麼 TIME_WAIT 狀態還需要等 2MSL 後才能返回到 CLOSED 狀態?
什麼是 2MSL?MSL 即 Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:「它(MSL)是任何報文段被丟棄前在網路內的最長時間。」那麼,2MSL 也就是這個時間的 2 倍,當 TCP 連接完成四個報文段的交換時,主動關閉的一方將繼續等待一定時間(2-4分鐘),即使兩端的應用程式結束。
為什麼需要2MSL呢?
雖然雙方都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,按理可以直接回到 CLOSED 狀態(就好比從 SYN_SEND 狀態到 ESTABLISH 狀態那樣);但是因為我們必須要假想網路是不可靠的,你無法保證你最後發送的 ACK 報文會一定被對方收到,因此對方處於 LAST_ACK 狀態下的 SOCKET 可能會因為超時未收到 ACK 報文,而重發 FIN 報文,所以這個 TIME_WAIT 狀態的作用就是用來重發可能丟失的 ACK 報文。
TIME_WAIT 狀態存在的理由:
-
可靠地實現 TCP 全雙工連接的終止
在進行關閉連接四路握手協議時,最後的 ACK 是由主動關閉端發出的,如果這個最終的 ACK 丟失,伺服器將重發最終的 FIN,因此客戶端必須維護狀態資訊允許它重發最終的 ACK。如果不維持這個狀態資訊,那麼客戶端將響應 RST 分節,伺服器將此分節解釋成一個錯誤(在 Java 中會拋出connection reset SocketException
)。因而,要實現 TCP 全雙工連接的正常終止,必須處理終止序列四個分節中任何一個分節的丟失情況,主動關閉 的客戶端必須維持狀態資訊進入 TIME_WAIT 狀態。 -
允許老的重複分節在網路中消逝
TCP 分節可能由於路由器異常而 「迷途」,在迷途期間,TCP 發送端可能因確認超時而重發這個分節,迷途的分節在路由器修復後也會被送到最終目的地,這個原來的迷途分節就稱為lost duplicate
。在關閉一個 TCP 連接後,馬上又重新建立起一個相同的 IP 地址和埠之間的 TCP 連接,後一個連接被稱為前一個連接的化身 (incarnation),那麼有可能出現這種情況,前一個連接的迷途重複分組在前一個連接終止後出現,從而被誤解成從屬於新的化身。為了避免這個情 況,TCP 不允許處於 TIME_WAIT 狀態的連接啟動一個新的化身,因為 TIME_WAIT 狀態持續 2MSL,就可以保證當成功建立一個 TCP 連接的時 候,來自連接先前化身的重複分組已經在網路中消逝。防止
lost duplicate
對後續新建正常鏈接的傳輸造成破壞,lost duplicate
在實際的網路中非常常見,經常是由於路由器產生故障,路徑無法收斂,導致一個 packet 在路由器 A,B,C 之間做類似死循環的跳轉。IP
頭部有個TTL
,限制了一個包在網路中的最大跳數,因此這個包有兩種命運,要麼最後TTL
變為 0,在網路中消失;要麼TTL
在變為 0 之前路由器路徑收斂,它憑藉剩餘的 TTL 跳數終於到達目的地。但非常可惜的是 TCP 通過超時重傳機制在早些時候發送了一個跟它一模一樣的包,並先於它達到了目的地,因此它的命運也就註定被 TCP 協議棧拋棄。另外一個概念叫做 incarnation connection,指跟上次的 socket pair 一摸一樣的新連接,叫做 incarnation of previous connection 。lost duplicate 加上 incarnation connection,則會對我們的傳輸造成致命的錯誤。大家都知道 TCP 是流式的,所有包到達的順序是不一致的,依靠序列號由 TCP 協議棧做順序的拼接;假設一個 incarnation connection 這時收到的 seq=1000, 來了一個 lost duplicate 為 seq=1000, len=1000, 則 TCP 認為這個 lost duplicate 合法,並存放入了 receive buffer,導致傳輸出現錯誤。通過一個 2MSL TIME_WAIT 狀態,確保所有的 lost duplicate 都會消失掉,避免對新連接造成錯誤。
該狀態為什麼設計在主動關閉這一方:
- 發最後 ack 的是主動關閉一方;
- 只要有一方保持 TIME_WAIT 狀態,就能起到避免 incarnation connection 在 2MSL 內的重新建立,不需要兩方都有。
對伺服器的影響
當某個連接的一端處於 TIME_WAIT 狀態時,該連接將不能再被使用。事實上,對於我們比較有現實意義的是,這個埠將不能再被使用。某個埠處於 TIME_WAIT 狀態(其實應該是這個連接)時,這意味著這個 TCP 連接並沒有斷開(完全斷開),那麼,如果你 bind 這個埠,就會失敗。對於伺服器而言,如果伺服器突然 crash 掉了,那麼它將無法在 2MSL 內重新啟動,因為 bind 會失敗。解決這個問題的一個方法就是設置 socket 的 SO_REUSEADDR 選項,這個選項意味著你可以重用一個地址。
如何正確對待 2MSL TIME_WAIT
RFC 要求 socket pair 在處於 TIME_WAIT 時,不能再起一個 incarnation connection。但絕大部分 TCP 實現,強加了更為嚴格的限制。在 2MSL 等待期間,socket 中使用的本地埠在默認情況下不能再被使用。若 A 10.234.5.5:1234
和 B 10.55.55.60:6666
建立了連接,A 主動關閉,那麼在 A 端只要 port 為 1234,無論對方的 port 和 ip 是什麼,都不允許再起服務。顯而易見這是比 RFC 更為嚴格的限制,RFC 僅僅是要求 socket pair 不一致,而實現當中只要這個 port 處於 TIME_WAIT,就不允許起連接。這個限制對主動打開方來說是無所謂的,因為一般用的是臨時埠;但對於被動打開方,一般是 server,就悲劇了,因為 server 一般是熟知埠。比如 http,一般埠是 80,不可能允許這個服務在 2MSL 內不能起來。解決方案是給伺服器的 socket 設置 SO_REUSEADDR 選項,這樣的話就算熟知埠處於 TIME_WAIT 狀態,在這個埠上依舊可以將服務啟動。當然,雖然有了 SO_REUSEADDR 選項,但 sockt pair 這個限制依舊存在。比如上面的例子,A 通過 SO_REUSEADDR 選項依舊在 1234 埠上起了監聽,但這時我們若是從 B 通過 6666 埠去連它,TCP 協議會告訴我們連接失敗,原因為 Address already in use。
2.2 UDP 協議
UDP 只在 IP 的數據報服務之上增加了很少一點的功能,即埠的功能和差錯檢測的功能,雖然 UDP 用戶數據報只能提供不可靠的交付,但 UDP 在某些方面有其特殊的優點。
UDP 數據報文分為兩個部分:首部和數據部分。
UDP首部欄位很簡單,由4個欄位組成,每個欄位的長度都是兩個位元組,共8位元組。
- 源埠 原埠號,在需要對方回信時選用,不需要時可全0
- 目的埠 目的埠號,這在終點交付報文時必須使用,不然數據交給誰呢?
- 長度 UDP的長度,最小值為8位元組,僅有首部
- 檢驗和 檢測用戶數據報在傳輸過程是否有錯,有錯就丟棄。
在傳輸的過程中,如果接收方UDP發現收到的報文中的目的埠不存在,會直接丟棄,然後由網際控制報文協議ICMP給發送方發送「埠不可達」差錯報文。
偽首部
計算校驗和時,需要在UDP之前增加12個位元組的偽首部。這種首部並不是用戶數據報的真正首部。偽首部並不在網路中傳輸,只是在計算檢驗和,臨時添加在UDP用戶數據報前,得到一個臨時的用戶數據報。
UDP的校驗和是把首部和數據部分一起校驗,發送方計算校驗和的一般步驟:
- 將首部的校驗和欄位填充為0(零);
- 把偽首部和用戶數據報UDP看出16位的字元串連接起來;
- 如果數據部分不是偶數位元組,則填充一個全零位元組(該位元組不發送到網路層);
- 按二進位反碼計算出這些16位字的和;
- 然後將和寫入校驗和欄位,就可以發送到網路層了。
接收方收到用戶數據報後,連同偽首部一起,按二進位反碼求這些16位字的和,無差錯結果是應全為1.否則出錯,直接丟棄該報文。
計算UDP校驗和的例子
UDP的主要特點:
- UDP 是無連接的,即發送數據之前不需要建立連接。
- UDP 使用盡最大努力交付,即不保證可靠交付,同時也不使用擁塞控制。
- UDP 是面向報文的。UDP 沒有擁塞控制,很適合多媒體通訊的要求。
- UDP 支援一對一、一對多、多對一和多對多的交互通訊。
- UDP 的首部開銷小,只有 8 個位元組。
面向報文的UDP:
- 發送方 UDP 對應用程式交下來的報文,在添加首部後就向下交付 IP 層。UDP 對應用層交下來的報文,既不合併,也不拆分,而是保留這些報文的邊界。
- 應用層交給 UDP 多長的報文,UDP 就照樣發送,即一次發送一個報文。
- 接收方 UDP 對 IP 層交上來的 UDP 用戶數據報,在去除首部後就原封不動地交付上層的應用進程,一次交付一個完整的報文。
2.3 TCP 協議如何保證傳輸的可靠性
既然要解決可靠性,那麼什麼是可靠性,以及什麼因素導致不可靠是首要回答的問題。
對於讀取 TCP 數據流而言,能保證數據的讀取是無損壞,不丟失,有序這三點就算解決了可靠性問題。
導致不可靠的因素有很多:
干擾
數據在物理線路上是以電子的方式傳輸,網路傳輸的不可靠因素會有很多,網路波動,線路抖動,硬體故障,電壓波動,都會導致數據的某些bit位發生變化(0 成了1 , 1 成了 0)。
亂序
發送方按照順序接連發送兩個數據包,但是可能因為第一個數據包在網路定址的過程中突然遇到網路不穩定的問題耽誤了一些時間,導致第二個數據包反而先到。這就需要協議去做區分誰是第一個包誰是第二個包。
丟包
丟包在網路傳輸中應該是很常見的現象。網路不穩定導數據包丟失,接收端收到的數據相當於丟失了部分數據。
冗餘
遇到網路風暴或者延遲的時候,發送方發送的數據包遲遲沒有收到接收方的返回確認,誤以為接收方沒有收到,把同樣的包重新發送一次,網路風暴結束,這個數據包重新找到了地址又送達了。但是對於接收當來說,同一個包收到了兩次,所以需要提供區分包是否重複接收的能力。
針對上面這些問題,TCP 約定了一些方案來解決傳輸可靠性問題,TCP協議保證數據傳輸可靠性的方式主要有:
- 校驗和;
- 序列號;
- 確認應答;
- 超時重傳;
- 連接管理;
- 流量控制;
- 擁塞控制。
校驗和
校驗和是一個 16bit 長的欄位,發送方在計算 checksum 時會先將報文中的 Checksum置零,然後基於整個報文(頭部 + 數據部分)計算出 checksum。
校驗和在 TCP 報文段中的位置位於上圖的藍色部分。
計算方式:將發送的數據段都當做一個 16 位的整數,將這些整數加起來。並且前面的進位不能丟棄,補在後面,最後取反,得到校驗和。
發送方:在發送數據之前計算檢驗和,並進行校驗和的填充。
接收方:收到數據後,對數據以同樣的方式進行計算,求出校驗和,與發送方的進行比對。
在發送數據時,為了計算數據包的校驗和。應該按如下步驟:
-
把校驗和欄位置為 0;
-
把需要校驗的數據看成以 16 位為單位的數字組成,依次進行二進位反碼求和;
-
把得到的結果存入校驗和欄位中。
在接收數據時,計算數據報的校驗和相對簡單,按如下步驟:
-
把首部看成以 16 位為單位的數字組成,依次進行二進位反碼求和,包括校驗和欄位;
-
檢查計算出的校驗和的結果是否等於零(反碼應為16個1);
-
如果等於零,說明被整除,校驗和正確。否則,校驗和就是錯誤的,協議棧要拋棄這個數據包。
所謂的二進位反碼求和,即為先進行二進位求和,然後對和取反。
序列號
TCP 將每個位元組的數據都進行了編號,這就是序列號,序列號的作用:
-
保證可靠性(當接收到的數據總少了某個序號的數據時,能馬上知道);
-
保證數據的按序到達;
-
提高效率,可實現多次發送,一次確認;
-
去除重複數據;
數據傳輸過程中的確認應答處理、重發控制以及重複控制等功能都可以通過序列號來實現。
確認應答機制(ACK)
TCP 通過確認應答機制實現可靠的數據傳輸。在 TCP 的首部中有一個標誌位 – ACK,此標誌位表示確認號是否有效。接收方對於按序到達的數據會進行確認,當標誌位 ACK=1 時確認首部的確認欄位有效。進行確認時,確認欄位值表示這個值之前的數據都已經按序到達了。而發送方如果收到了已發送的數據的確認報文,則繼續傳輸下一部分數據;而如果等待了一定時間還沒有收到確認報文就會啟動重傳機制。
超時重傳機制
第一種情況:數據包丟失。當數據發出後在一定的時間內未收到接收方的確認,發送方就會進行重傳(通常是在發出報文段後設定一個特定的時間間隔,到點了還沒有收到應答則進行重傳)。
第二種情況:確認包丟失。當接收方收到重複數據(通過序列號進行識別)的時就將其丟棄,重新發送ACK。
重傳時間的確定:報文段發出到確認中間有一個報文段的往返時間 RTT,顯然超時重傳時間 RTO 會略大於這個 RTT,TCP 會根據網路情況動態的計算 RTT,即 RTO 是不斷變化的。在 Linux 中,超時以 500ms 為單位進行控制,每次判定超時重發的超時時間都是 500ms 的整數倍。其規律為:如果重發一次仍得不到應答,就等待 2*500ms 後再進行重傳,如果仍然得不到應答就等待 4*500ms 後重傳,依次類推,以指數形式遞增,重傳次數累計到一定次數後,TCP認為網路或對端主機出現異常,就會強行關閉連接。
連接管理
連接管理即我們上面講過的3次握手和四次揮手。
流量控制
所謂流量控制,就是讓發送端不要發送的過快,讓接收端能來得及接收。
假設沒有流量控制發送端發送的速度太快導致接收端的接收緩衝區很快填滿,此時發送端如果繼續發送數據接收端處理不過來,這時接收端就會把本來應該接收的數據丟棄,這會觸發發送端的重發機制,從而導致網路流量的無端浪費。
所以TCP需要提供一種機制:讓發送端根據接收端實際的接收能力控制發送的數據量。這就是所謂的流量控制。
TCP 利用 滑動窗口 實現流量控制的機制, 而滑動窗口大小是通過TCP首部的窗口大小欄位來通知對方。
TCP頭裡有一個欄位叫Window,又叫Advertised-Window,這個欄位是接收端告訴發送端自己還有多少緩衝區可以接收數據。於是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收端處理不過來。
上圖中我們可以看到:
- 接收端 LastByteRead 指向了 TCP 緩衝區中讀到的位置,NextByteExpected 指向的地方是收到的連續包的最後一個位置,LastByteRcved 指向的是收到的包的最後一個位置,我們可以看到中間有些數據還沒有到達,所以有數據空白區。
- 發送端的 LastByteAcked 指向了被接收端 Ack 過的位置(表示成功發送確認),LastByteSent 表示發出去了,但還沒有收到成功確認的 ack,LastByteWritten 指向的是上層應用正在寫的地方。
所以接收端在給發送端回 ACK 中會彙報自己的 AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1,而發送方會根據這個窗口來控制發送數據的大小,以保證接收方可以處理。
下面我們來看一下發送方的滑動窗口示意圖:
上圖中分成了四個部分,分別是:(其中那個黑模型就是滑動窗口)
- 已收到ack確認的數據;
- 已發出但還沒收到ack的;
- 在窗口中還沒有發出的(接收方還有空間);
- 窗口以外的數據(接收方沒空間)。
注意:
滑動窗口裡是已發出但未收到 ACK、還未發出的數據。
下面是個滑動後的示意圖(收到36的ack,並發出了46-51的位元組)
TCP是以段為單位進行數據包的發送的。
- 在建立TCP連接的同時,也可以確定發送數據包的單位,稱之為「最大消息長度」:MSS。最理想的情況是,最大消息長度MSS正好是IP層中不被分片處理的最大數據長度。
- TCP在傳送大量數據的時候,是以「段=MSS的大小」將數據進行分割發送的,進行重發時也是以MSS為單位的。
- 最大消息長度——MSS是在三次握手的時候,在兩端主機之間被計算得出的。兩端主機在發出「建立TCP連接請求的SYN包」時,會在SYN包的TCP首部中寫入MSS選項,告訴對方自己所能夠適應的MSS的大小,然後發送端主機會在兩者之間選擇一個較小的MSS值投入使用。
零處理的窗口
當左右邊界相同時稱為零窗口,一般由於接收方無法及時處理數據,為了阻止發送方繼續發送數據而產生這一情況。當接收方可以接收數據時,會給發送端一個純 ACK,稱為 窗口更新。然而這一數據報並沒有確認機制,所以要確保能處理這一丟包。
如果發生這一丟包,雙方都會繼續等待,產生死鎖。發送方為了解決這一情況,有一個持續計時器來觸發 窗口探測 機制。接收方收到後必須返回一個帶有窗口大小的 ACK。RFC 中推薦計時器時間為一個 RTO,之後以指數級增長。
Negle演算法
Negle 演算法主要為了解決 TCP 的傳輸效率問題。Negle 演算法規定:若要把發送的數據逐個位元組快取起來,則發送方需要把第一個位元組發送出去,然後快取後面的位元組,在收到接收方第一個位元組的確認,再將現有快取中所有位元組組成一個報文段發送出去,繼續快取後續數據。只有在收到前一個報文的確認之後發送後面的數據。這是為了減少所用頻寬。當發送數據到達 TCP 發送窗口的一半或已達到報文段的最大長度也會立即發送報文段,而不是等待接收方確認。這是為了提高網路吞吐量。
糊塗窗口綜合征
TCP 接收方的快取已滿,若上層一次從快取中讀取一個位元組,這樣接收方就可以繼續接納一個位元組的窗口,然後向發送方發送確認,把窗口設為 1 個位元組(上文所講,IP 數據報為 41 位元組長)。如果這樣持續下去,那麼網路效率非常低。
所以有效的解決方法,就是讓接收方等待一定時間,讓快取空間能夠接納一個最長的報文段,或者等待接收快取已有一半的空閑空間,再發出確認報文和通知當前窗口大小。
擁塞控制
擁塞:即對資源的需求超過了可用的資源。若網路中許多資源同時供應不足,網路的性能就要明顯變壞,整個網路的吞吐量隨之負荷的增大而下降。
擁塞控制所要做的都有一個前提:網路能夠承受現有的網路負荷。擁塞控制是一個全局性的過程,涉及到所有的主機、路由器,以及與降低網路傳輸性能有關的所有因素。
幾種擁塞控制方法:
- 慢開始( slow-start )
- 擁塞避免( congestion avoidance )
- 快重傳( fast retransmit )
- 快恢復( fast recovery )
慢開始和擁塞避免
發送方維持一個擁塞窗口 cwnd ( congestion window )的狀態變數。擁塞窗口的大小取決於網路的擁塞程度,並且動態地在變化。發送方讓自己的發送窗口等於擁塞。
發送方控制擁塞窗口的原則是:只要網路沒有出現擁塞,擁塞窗口就再增大一些,以便把更多的分組發送出去。但只要網路出現擁塞,擁塞窗口就減小一些,以減少注入到網路中的分組數。
慢開始演算法
當主機開始發送數據時,如果大量數據位元組注入到網路,那麼就有可能引起網路擁塞,因為現在並不清楚網路的負荷情況。因此較好的方法是先探測一下,即由小到大逐漸增大發送窗口,也就是說,由小到大逐漸增大擁塞窗口數值。
慢開始指主機由小到大逐漸增大發送窗口,即增大擁塞窗口的數值。初始擁塞窗口 cwnd 設置為不超過 2 到 4 個最大報文段MSS的數值,具體規定:
- 若
MSS ≤ 1095
位元組,cwnd = 4 x MSS
位元組,不得超過 4 個報文段。 - 若
MSS > 1095
且≤ 2190
位元組,cwnd = 3 X MSS
位元組,不得超過 3 個報文段。 - 若
MSS > 2190
位元組,則cwnd = 2 x MSS
位元組,不得超過 2 個報文段。
上面的規定限制了初始擁塞窗口的大小。慢開始在每收到一個對新的報文段的確認後,cwnd 就可以增加最多一個 MSS 的數值。
擁塞窗口 cwnd 每次的增加量 = min(N, MSS)
N是剛收到確認的報文段所確認的位元組數,當N < MSS時,擁塞窗口每次的增加量要小於 MSS。下文舉例說明慢開始的原理(實際上,TCP 的窗口是以位元組大小為單位,下文為了方便以報文端形容):
從圖可知,初始化窗口為1,所有發送 M1 報文段,收到確認號之後,發送 M2-M3 兩個報文段,因為擁塞窗口增大了,後面的輪次也是這樣翻倍增加的。隨著輪次的增多,那麼發送到網路的數據就會急劇增加,容易出現擁塞,因此需要慢開始門限(ssthresh)狀態變數。
- 當 cwnd < ssthresh 時,使用慢開始演算法;
- 當 cwnd > ssthresh 時,使用擁塞避免演算法;
- 當 cwnd = ssthresh 時,慢開始或者擁塞避免演算法。
擁塞避免演算法:讓擁塞窗口 cwnd 緩慢地增大,即每經過一個往返時間 RTT 就把發送方的擁塞窗口 cwnd 加 1,而不是加倍。這樣擁塞窗口 cwnd 按線性規律緩慢增長,比慢開始演算法的擁塞窗口增長速率緩慢得多。
當網路出現擁塞時:
- 無論在慢開始階段還是在擁塞避免階段,只要發送方判斷網路出現擁塞(其根據就是沒有按時收到確認),就要把慢開始門限 ssthresh 設置為出現擁塞時的發送方窗口值的一半(但不能小於2)。
- 然後把擁塞窗口 cwnd 重新設置為 1,執行慢開始演算法。
- 這樣做的目的就是要迅速減少主機發送到網路中的分組數,使得發生擁塞的路由器有足夠時間把隊列中積壓的分組處理完畢。
快重傳與快恢復
在 TCP/IP 中,快速重傳和恢復(fast retransmit and recovery,FRR)是一種擁塞控制演算法,它能快速恢復丟失的數據包。沒有 FRR,如果數據包丟失了,TCP 將會使用定時器來要求傳輸暫停。在暫停的這段時間內,沒有新的或複製的數據包被發送。有了 FRR,如果接收機接收到一個不按順序的數據段,它會立即給發送機發送一個重複確認。如果發送機接收到三個重複確認,它會假定確認件指出的數據段丟失了,並立即重傳這些丟失的數據段。有了 FRR,就不會因為重傳時要求的暫停被耽誤。
快恢復演算法 :
- 當發送端收到連續三個重複的確認時,就執行「乘法減小」演算法,把慢開始門限 ssthresh 減半。但接下去不執行慢開始演算法。
- 由於發送方現在認為網路很可能沒有發生擁塞,因此現在不執行慢開始演算法,即擁塞窗口 cwnd 現在不設置為 1,而是設置為慢開始門限 ssthresh 減半後的數值,然後開始執行擁塞避免演算法(「加法增大」),使擁塞窗口緩慢地線性增大。