二、傳輸層:TCP、UDP

目錄
一、傳輸層
二、UDP
 1、UDP的數據格式
 2、UDP頭部
三、TCP
 1、TCP的數據格式
 2、TCP頭部
 3、TCP是可靠傳輸(詳述)
 4、TCP是面向連接的(詳述)


一、傳輸層


傳輸層有2個協議:

  • TCP(Transmission Control Protocol),傳輸控制協議。
  • UDP(User Datagram Protocol),用戶數據報協議。

TCP和UDP的區別:

  • 連接性:TCP是面向連接的,UDP是無連接的。這句話什麼意思?這裏先通俗地說一下,後面會用專業知識解釋:比如說客戶端向服務器發送一個請求,如果走的是TCP,就必須先經過三次握手建立連接,連接成功後才發送真正的數據,數據發送完還必須經過四次揮手斷開連接;如果走的是UDP,則客戶端不會跟服務器建立連接,而是直接朝着服務器扔數據。
  • 可靠性:TCP是可靠傳輸,UDP是不可靠傳輸。這句話什麼意思?這裏先通俗地說一下,後面會用專業知識解釋:比如說客戶端向服務器發送100K的數據,如果走的是TCP,則傳輸過程中丟了某些字節,TCP會重傳,確保這100K的數據完整;如果走的是UDP,則傳輸過程中丟了某些字節,UDP就不管了,這100K的數據就不完整了。
  • 傳輸速率和延遲:TCP慢、延遲大,UDP快、延遲小。因爲TCP是面向連接的,建立連接、保持連接和斷開連接都要開銷,而且是可靠傳輸,重傳、流量控制、擁塞控制也都要開銷,所以相對來說,TCP慢、延遲大,UDP快、延遲小。
  • 應用場景:如果我們對數據的完整性要求較高,丟一個包都不行,則使用TCP;如果我們對數據的傳輸速率和實時性要求較高,數據丟那麼幾個包也沒關係,則使用UDP。比如說我們通過瀏覽器訪問一個網頁(應用層對應協議爲HTTP、HTTPS),肯定是希望一個完整的網頁展示在用戶面前,不可能說網頁裏丟了一段文字還展示給用戶,文件傳輸(應用層對應協議爲FTP)、郵件發送(應用層對應協議爲SMTP)也是同樣道理,它們用的都是TCP;而音視頻通話、直播(應用層對應協議RTP等,當然也有RTMP使用的是TCP),我們肯定是希望快速地、實時地傳輸當前時間節點的數據給對方,有些畫面丟了也沒什麼大不了,這種用的就是UDP了。
  • 應用層對應協議:TCP——HTTP、HTTPS、FTP、SMTP、RTMP等,UDP—— RTP等。


二、UDP


1、UDP的數據格式

UDP的數據格式 = UDP頭部 + UDP數據部分(即應用層傳下來的真正數據)。

2、UDP頭部

UDP是無連接的,而且是不可靠傳輸,所以它的頭部就不需要維護很多複雜的參數來做這兩件事,固定佔8個字節。

  • 源端口號:這2個字節存儲的是源端口號。假設我們是客戶端訪問服務器,客戶端的端口都是臨時開啓的隨機端口,這次請求結束後,這個隨機端口就不佔用了,因爲佔2個字節,所以隨機端口共2^16=65536個,隨機端口號取值範圍爲0~65535。(那多個客戶端隨機到同一個端口號會不會有問題?不會,因爲數據傳輸過程中還會攜帶客戶端和服務器的唯一標識——IP地址,所以不會出現錯亂)
  • 目標端口號:這2個字節存儲的是目標端口號。假設我們是客戶端訪問服務器,一臺服務器上會開啓很多個端口用來監聽來自客戶端的請求,每一個端口裏都啓動着一個服務器軟件———Tomcat,每一個Tomcat裏又部署着若干個Java項目,可見端口和Tomcat是一對一的,Tomcat和項目是一對多的,所以如果想訪問某個項目,就必須得確定好是哪個Tomcat,也即必須確定好是哪個端口,目標端口號指的就是這個端口。(服務器的網卡收到數據後,就會根據目標端口號將數據投遞到相應的端口,也即投遞給相應的Tomcat去處理)
  • UDP長度:這2個字節存儲的是UDP整個數據段的長度,即UDP頭部的長度 + UDP數據部分的長度。
  • UDP檢驗和:這2個字節存儲的是UDP檢驗和。UDP檢驗和是UDP僞頭部 + UDP頭部 + UDP數據部分算出來的這麼一個數值(UDP僞頭部僅在計算UDP檢驗和時才生成、纔有作用,它並不會被傳遞給網絡層)。UDP檢驗和提供了差錯檢驗的功能,用來驗證UDP整個數據段是否有誤(有的數據可能在傳輸過程中發生0-1翻轉,即數據出錯)。假設我們是客戶端訪問服務器,那麼當服務器收到UDP整個數據段後,也會按照同樣的規則算出來一個數值,如果兩個值一樣,就ok;如果兩個值不一樣,就會認爲傳輸過程中發生了錯誤,於是丟掉該數據段(但是因爲UDP是不可靠傳輸,丟掉就丟掉了,不會重傳)


三、TCP


1、TCP的數據格式

TCP的數據格式 = TCP頭部 + TCP數據部分(即應用層傳下來的真正數據)。

2、TCP頭部

TCP是面向連接的,而且是可靠傳輸,所以它的頭部就需要維護很多複雜的參數來做這兩件事,至少佔20個字節,最多佔60個字節,也就是說有40個字節是可選的。

  • 源端口號:這2個字節存儲的是源端口號。
  • 目標端口號:這2個字節存儲的是目標端口號。
  • 序號(Sequence Number):這4個字節存儲的是一個數值,這個數值代表的是當前TCP數據段的偏移量(具體地說,是當前TCP數據段的數據部分的首字節相對於應用層數據首字節的偏移量 + 1)。通常出現在發送方的TCP頭部裏,代表當前我給你發送的是哪個字節開始的數據,接收方收到數據段後就可以根據這個數值做排序。比如應用層數據有1200個字節,這1200個字節的數據到了傳輸層後會被切成十二段M1 ~ M12,每段100個字節,那麼這十二段數據分別加上TCP頭部後就是H1M1 ~ H12M12,則H1裏存儲的Seq = 1、H2裏存儲的Seq = 101、H11裏存儲的Seq = 1001、H12裏存儲的Seq = 1101。(但實際上爲了防止攻擊,序號不是直接存儲1、101、1001、1101這麼簡單明確的數據,它會有一個隨機初始序號j,然後再加上1、101、1001、1101。因爲佔4個字節,所以隨機初始序號j的取值範圍差不多就是0 ~ 2^32=4294967296)
  • 確認號(Acknowledgment Number):這4個字節存儲的是一個數值,這個數值代表的是希望對方下一次傳過來的TCP數據段的偏移量(具體地說,是希望對方下一次傳過來的TCP數據段的數據部分的首字節相對於應用層數據首字節的偏移量 + 1),注意只有當ACK = 1時,確認號纔有意義。通常出現在接收方的TCP頭部裏,代表我希望你下次給我發送哪個字節開始的數據,發送方收到數據段後就可以根據這個數值發送相應的數據段。比如我已經收到對方1 ~ 100字節的數據了,希望對方下一次給我傳101 ~ 200字節的數據,那我就會給對方回覆一個“ACK = 1,Ack = 101”的確認數據。(但實際上爲了防止攻擊,確認號也不是直接存儲101這麼簡單明確的數據,它也會有一個隨機初始確認號k,然後再加上101。因爲佔4個字節,所以隨機初始確認號k的取值範圍差不多就是0 ~ 2^32=4294967296)
  • 數據偏移:這4位(半個字節)存儲的是TCP頭部的長度信息。它裏面存儲的值乘以4就是TCP頭部的長度,上面我們知道TCP頭部的長度至少佔20個字節,所以這個值最小是5(十六進制0x0101),反過來這4位能存儲的最大值十六進制0x1111(十進制15),所以我們就知道了TCP頭部的長度最多佔60個字節,有40個字節是可選的。(上面我們知道UDP頭部裏有2個字節記錄了整個UDP數據段的長度,那這裏爲什麼TCP頭部沒有記錄整個TCP數據段的長度呢?其實UDP頭部裏記錄整個UDP數據段的長度是可有可無的,那2個字節純粹是爲了保證UDP頭部的32位對齊,UDP和TCP數據部分長度其實都可以靠網絡層計算得來。我們知道網絡層一個IP數據包頭部裏會存儲IP頭部的長度和整個IP數據包的長度,所以可以計算得到IP數據部分長度 = 整個IP數據包的長度 - IP頭部的長度,而IP數據部分長度 = UDP頭部長度 + UDP數據部分長度 || IP數據部分長度 = TCP頭部長度 + TCP數據部分長度,所以UDP數據部分長度 = IP數據部分長度 - UDP頭部長度固定的8個字節 || TCP數據部分長度 = IP數據部分長度 - TCP頭部長度
  • 保留:這6位(一個半字節)是保留空間,暫時沒什麼用,將來可能會有用,存的全是0。
  • URG標誌位(Urgent,緊急):緊急標誌位。當URG = 1時,緊急指針那2個字節纔有意義,代表當前TCP數據段內有緊急重要的數據,應該優先儘快傳遞。
  • ACK標誌位(Acknowledgment,回覆、確認):回覆標誌位、確認標誌位,通常出現在接收方的TCP頭部裏,當ACK = 1時,代表我收到了你的消息、給你個回覆。
  • PSH標誌位(Push):一般用不上。
  • RST標誌位(Reset,重置):重置標誌位。當RST = 1時,代表當前TCP連接出現了嚴重問題,必須斷開連接,如有需要可重新建立連接。
  • SYN標誌位(Sync,建立連接、同步):建立連接標誌位、同步標誌位,既有可能出現在發送方的TCP頭部裏,也有可能出現在接收方的TCP頭部裏。當SYN = 1時,代表一個希望跟你建立連接的請求。
  • FIN標誌位(Finish,斷開連接、結束):斷開連接標誌位、結束標誌位,既有可能出現在發送方的TCP頭部裏,也有可能出現在接收方的TCP頭部裏。當FIN = 1時,代表一個希望跟你斷開連接的請求。
  • 窗口(Window):這2個字節存儲的是一個數值——被稱作窗口大小,用來告訴對方下次最多能給我傳輸多少個字節的數據。通常出現在接收方的TCP頭部裏,發送方收到數據段後就可以根據這個數值控制發送的數據量了,一般用來做流量控制。
  • 檢驗和:這2個字節存儲的是TCP檢驗和。跟UDP一樣,TCP檢驗和是TCP僞頭部 + TCP頭部 + TCP數據部分算出來的這麼一個數值,TCP檢驗和提供了差錯檢驗的功能。假設我們是客戶端訪問服務器,那麼當服務器收到TCP整個數據段後,也會按照同樣的規則算出來一個數值,如果兩個值一樣,就ok;如果兩個值不一樣,就會認爲傳輸過程中發生了錯誤,於是丟掉該數據段(但是TCP是可靠傳輸,會重傳)
  • 緊急指針:這2個字節存儲的是當前TCP數據段內緊急重要數據的信息。這2個字節要和URG標誌位聯合使用,當URG = 0時,這2個字節沒有意義;當URG = 1時,這2個字節纔有意義,裏面存儲的是一個長度——比如8個字節,代表當前TCP數據段內前8個字節是緊急重要的數據,應該優先儘快傳遞。
  • 選項:TCP頭部的可選空間。如SACK(Selective Acknowledgment)選擇性確認——SACK信息會放在TCP頭部的選項裏,裏面有一個字段叫Kind,如果Kind設置值爲5則代表使用Sack,此時Kind字段後面會有若干個字節攜帶哪些數據段丟失掉了的信息,以便發送方據此做選擇性重傳;如果Kind設置值不爲5則代表不使用Sack,發送方就不做選擇性重傳,而是根據窗口做全部重傳。又如MSS(Maximum Segment Size)每個數據段最大大小,對方切TCP數據段的時候要依據這個數值的,不能超過這個數值。
  • 填充:當TCP頭部長度基本確定下來後,如果不是4的倍數,這個空間負責填充成4的倍數,因爲數據偏移那裏要求了TCP頭部長度必須是4的倍數。

總結:TCP頭部有那麼多的數據,其中有幾個是需要着重關心的。

  • 通常出現在發送方TCP頭部裏的(當然它肯定也會出現在接收方TCP頭部裏,只不過我們不需要太關心它的意義而已):
    (1)序號Seq,代表當前我給你發送的是哪個字節開始的數據,接收方收到數據段後就可以根據這個數值做排序。
  • 通常出現在接收方TCP頭部裏的(當然它肯定也會出現在發送方TCP頭部裏,只不過我們不需要太關心它的意義而已):
    (1)確認號Ack,必須與確認標誌位ACK = 1連用,代表我希望你下次給我發送哪個字節開始的數據,發送方收到數據段後就可以根據這個數值發送相應的數據段。
    (2)窗口Win,用來告訴對方下次最多能給我傳輸多少個字節的數據,發送方收到數據段後就可以根據這個數值控制發送的數據量了。
  • 同時出現在發送方、接收方TCP頭部裏的:
    (1)建立連接標誌位SYN,當SYN = 1時,代表一個希望跟你建立連接的請求。
    (2)回覆標誌位ACK,當ACK = 1時,代表我收到了你的消息、給你個回覆。
    (3)斷開連接標誌位FIN,當FIN = 1時,代表一個希望跟你斷開連接的請求。

3、TCP是可靠傳輸

可靠傳輸是什麼?可靠傳輸是指傳輸過程中不丟包、數據有序。

爲什麼要做可靠傳輸?就是爲了保證傳輸過程中不丟包、數據有序。

怎麼做到可靠傳輸的?

3.1 最初的做法:一段一段傳 + 一段一段回覆 + 超時重傳

假設A要給B發送300個字節的數據,應用層這300個字節的數據到了傳輸層後會被切成三段M1、M2、M3。(數據分段和數據重組是在OSI參考模型的傳輸層中完成的)

  • 情況一:正常傳輸

A會把“M1”先發送給B,B收到“M1”後會給A發送一個“確認收到M1”的回覆,A收到“確認收到M1”後就給B發送“M2”......如此循環,直到“M3”發送完畢。

  • 情況二:發送數據丟失或者發送數據有誤

A會把“M1”先發送給B,但是在傳輸過程中“M1”不小心丟了、根本就沒傳輸到B,或者“M1”沒丟、傳輸到B了、但是B經過差錯檢驗發現這段數據有誤、丟掉這段數據,那B根本就不會給A發送“確認收到M1”的回覆,那A就遲遲無法收到B的回覆、沒法發下一段數據了......其實A這邊會有一個定時器,過了超時時間如果還沒有收到B的回覆,A就會認爲“M1”傳輸出錯了、進而執行超時重傳“M1”......如此循環,直到“M3”發送完畢。

  • 情況三:確認數據丟失或者確認數據有誤

A會把“M1”先發送給B,B收到“M1”後會給A發送一個“確認收到M1”的回覆,但是在傳輸過程中“確認收到M1”不小心丟了、根本就沒傳輸到A,或者“確認收到M1”沒丟、傳輸到A了、但是A經過差錯檢驗發現這段數據有誤、丟掉這段數據,那A都會認爲“M1”傳輸出錯了、進而重傳“M1”,B再次收到“M1”後會丟掉重複的“M1”並再次給A發送一個“確認收到M1”的回覆.....如此循環,直到“M3”發送完畢。

  • 情況四:確認數據遲到或者發送數據遲到

A會把“M1”先發送給B,B收到“M1”後會給A發送一個“確認收到M1(第一次)”的回覆,但是在傳輸過程中這個回覆可能選了個較遠的路徑或者網不好,那A就會遲遲無法收到B的回覆了,過了超時時間如果還沒有收到,A就會認爲“M1”傳輸出錯了、進而超時重傳“M1”,B再次收到“M1”後會丟掉重複的“M1”並再次給A發送一個“確認收到M1(第二次)”的回覆,A收到“確認收到M1(第二次)”後就給B發送“M2”,此時“M2”發出去了,A才收到“確認收到M1(第一次)”,那A就什麼都不做、不會再發“M2”了......如此循環,直到“M3”發送完畢。

同理,A會把“M1”先發送給B,如果“M1”遲到了、過了超時時間纔到達B,也會導致一連串反應,但是這一套處理可以避免這種情況出錯。

  • 疑問:若有一段數據重傳了N次還是沒有成功,會一直重傳直至成功嗎?

不會,重傳了N次還是沒有成功,會被判定爲當前連接出了嚴重問題,一般都是斷開連接、重新建立連接,比如有些操作系統重傳了5次還是沒有成功,就會發送reset數據段(RST)了。

綜上:

一段一段傳 + 一段一段回覆 + 超時重傳可以保證TCP傳輸的可靠性。我給你傳一段你給我個回覆,我再傳下一段,如果你不給我回復我就給你超時重傳、直到你給我回復,這肯定能保證不丟包、數據有序。

但是:

這種做法的傳輸效率很低,是個串行操作,下一段數據必須得等上一段數據完完全全發送結束後才能發送。

3.2 優化後的做法:多段一起傳 + 多段一起回覆 + 被動重傳(是否使用SACK選擇性確認)

假設A要給B發送1200個字節的數據,應用層這1200個字節的數據到了傳輸層後會被切成十二段M1 ~ M12,每段100個字節。

因爲是多段一起傳,如果你一下子傳太多段,超過了接收方的緩存區大小,就會導致丟包,所以A與B三次握手建立連接時,B會通過窗口Win告訴A下次最多能給它傳輸多少個字節的數據,比如是400個字節。

那A會把“M1”(序號Seq = 1,代表我給你發送的是第1個字節開始開始的數據)、“M2”(序號Seq = 101,我給你發送的是第101個字節開始開始的數據)、“M3”(序號Seq = 201,我給你發送的是第201個字節開始開始的數據)、“M4”(序號Seq = 301,我給你發送的是第301個字節開始開始的數據)四個數據段一起發送給B(注意:一起發送不是指“M1”、“M2”、“M3”、“M4”合併成一個大段通過一次傳輸一起發送過去,它們還是四個段,還是四次傳輸,一起發送是指一次性發起、並行傳輸,哪一個數據段會先到達也不一定),B收到“M4”後會給A發送一個“確認收到M4”(回覆標誌位ACK = 1,代表給你個回覆,同時也使確認號Ack有意義;確認號Ack = 401,代表B希望A下次給它發送第401個字節開始的數據。注意:B只要回覆“確認收到M4”,就意味着它全部收到了“M1”、“M2 ”、“M3 ”、“M4 ”,如果丟失了其中的某一個,它就不會回覆“確認收到M4”,可能是回覆別的,下面會說到)。

A收到“確認收到M4”後就開始給B發送“M5”、“M6”、“M7”(序號Seq = 601,代表我給你發送的是第601個字節開始開始的數據)、“M8”,但是在傳輸過程中“M7”不小心丟了,那B收到“M5”、“M6”、“M8”後過了超時時間如果發現“ M7”還沒到,它就不會給A發送“確認收到M8”,而是給A發送“確認收到M6”(回覆標誌位ACK = 1,代表給你個回覆,同時也使確認號Ack有意義;確認號Ack = 601,代表B希望A下次給它發送第601個字節開始的數據,因爲丟掉的M7剛好就是第601個字節開始的數據)。

A收到“確認收到M6”後就開始給B發送“M7”、“M8”、“M9”、“M10”,但是A發現B發給它的“確認收到M6”是選擇性確認(也是三次握手是B告訴A的),於是A又會拿着“確認收到M6”的TCP頭部的選項去做判斷,發現之前已經成功發過“M8”了,於是就不傳“M8”了,只給B發送“M7”、“M9”、“M10”(當然如果三次握手時B告訴A不使用SACK選擇性確認,A就會重複發“M8”),那B收到“M7”、“M9”、“M10”就會按照序號Seq把“M7”放在它該在的位置上,保證有序,然後給A發送一個“確認收到M10”......如此循環,直到“M12”發送完畢。

綜上:

多段一起傳 + 多段一起回覆 + 被動重傳(是否使用SACK選擇性確認)可以保證TCP傳輸的可靠性。它就是通過TCP頭部的序號Seq來保證數據有序,通過被動重傳來保證不丟包(當然超時重傳是保留下來的)。

同時:

通過多段一起傳(是個並行操作) + 多段一起回覆 + SACK選擇性確認來優化提高了傳輸效率

3.3 流量控制

流量控制是什麼?接收方動態地告訴發送方發送的數據量不要太大,以便接收方能來得及處理。

爲什麼要做流量控制?如果接收方的緩存區滿了,發送方還在瘋狂地發送數據,接收方就只能把放不下的數據段丟掉,這樣大量的丟包會極大的浪費網絡資源,所以要做流量控制。據此我們看出流量控制也是保證TCP不丟包的一個手段。

怎麼做到流量控制的?接收方的傳輸層有個緩存區,它接收到的數據都會先放到緩存區,然後再傳遞給應用層。接收方會根據緩存區的狀態,動態地調整回覆TCP數據段裏的Win窗口大小,來告訴發送方下次最多能給它傳輸多少個字節的數據,以此來控制發送方發送的數據量———發送方發送的數據大小不能超過接收方給出的窗口大小,當接收方給出的窗口大小爲0時、說明接收方緩存區已經滿了、發送方會停止發送數據、如果緩存有了空間、打算讓發送發繼續發送數據、則發送確認數據窗口大小大於0即可。

3.4 擁塞控制

我們看上圖網絡中的鏈路A、鏈路B、鏈路C,鏈路A的帶寬爲1000M,所以理論上來講鏈路B和鏈路C每秒傳輸的數據量之和只要小於等於1000M,那鏈路A就可以不丟包地把數據從路由器3傳輸到路由器4,但是如果鏈路B和鏈路C每秒傳輸的數據量之和大於1000M,那鏈路A就會出現過載、進而丟包了,因爲它傳不了這麼多的數據。

擁塞控制是什麼?防止過多的數據注入到網絡中。(擁塞控制是一個全局的概念,它針對的是整個網絡,涉及到了所有計算機、路由器、交換機等,就像全局統籌整個城市的交通那樣;而流量控制是一個點對點的概念,它針對的是兩臺計算機之間,就像國慶期間從長安街東駛入的車輛到長安街西的限流一樣。一言以蔽之,擁塞控制控制的是整個網絡,流量控制控制的是對方計算機發出的數據量)

爲什麼要做擁塞控制?避免鏈路過載、大量丟包。據此我們看出擁塞控制也是保證TCP不丟包的一個手段。

怎麼做到擁塞控制的?慢開始 + 擁塞避免 + 快恢復。

  • 慢開始:A與B三次握手建立連接時,B會通過窗口Win告訴A下次最多能給它傳輸多少個字節的數據,比如是3000個字節;B還會通過選項區每個數據段最大大小MSS告訴A每個數據段最大是多大,比如是100個字節;這樣A就會下次把要發送的3000個字節的數據切成30個數據段,每段100個字節。接下來實際上A並不是把這30個數據段通過“下次”這一次全都傳給B,而是第一次傳2 ^ 0 = 1個數據段100個字節過去,如果收到了回覆、則代表網絡狀況良好、那我就試試傳更多的數據段過去,於是第二次傳2 ^ 1 = 2個數據段200個字節過去,如果收到了回覆、則代表網絡狀況良好、那我就試試傳更多的數據段過去,於是第三次傳2 ^ 2 = 4個數據段400個字節過去......如此循環,一次一次試探着傳輸、避免一次性往網絡中投放大量的數據段、指數級增長,這就是慢開始。

  • 擁塞避免:但是因爲慢開始是指數級增長的,如果一直任由它指數級增長下去,很快就會出現某一次傳輸的數據量變得超級大,從而導致網絡過早出現擁塞。所以慢開始是有一個閾值的,到了閾值後,就不會在指數級增長了,而是改作線性增長,每次多加一點、每次多加一點,防止網絡過早出現擁塞(如果一直是慢開始的話,可能傳六次就會導致網絡擁塞了,但其實第五次和第六次傳輸之間可能還有很大的空間可以利用,而擁塞避免就充分利用了第五次和第六次傳輸之間的空間,可能傳十三次纔會導致網絡擁塞,大大地將網絡擁塞的可能性延後),這就是擁塞避免。

  • 重新慢開始 + 擁塞避免:擁塞避免加到一定程度,一旦真得發生了網絡擁塞——一個很直觀的體現就是開始丟包,發送方就能通過“我都好幾個回覆沒收到了”感知到可能是發生了網絡擁塞,於是重新慢開始 + 擁塞避免,並且將慢開始的閾值重新設定爲發生網絡擁塞時傳輸數據量的一半。

  • 快恢復:但是“重新慢開始 + 擁塞避免”的慢開始無疑會使數據的傳輸效率大大降低,所以新版本的TCP協議裏引入了快恢復。擁塞避免加到一定程度,一旦真得發生了網絡擁塞,會直接開始下一次擁塞避免,而下一次擁塞避免的起點就是發生網絡擁塞時傳輸數據量的一半,這就是快恢復。

4、TCP是面向連接的

連接是什麼?這裏的連接肯定不是個物理連接(如網線、光纖等),甚至連個虛擬的連接都算不上(虛擬的連接至少讓我們感覺到它是個類似於通道一樣的東西),這裏的連接其實就是指通信雙方的一些初始化數據,如通信雙方的Win窗口大小、通信雙方的MSS每個數據段最大大小、通信雙方是否支持SACK選擇性確認、通信雙方的隨機初始序號、通信雙方的隨機初始確認號等,我們之所以把這些數據稱之爲“連接”,就是因爲通信雙方彼此知道對方的這些數據,這種“彼此知道對方數據”的狀態——我知道你、你知道我,就好像是有某種“連接”把通信雙方關聯起來了一樣,所以我們才把這些數據稱之爲“連接”了。(注意:源端口號、目標端口號、源IP、目標IP這四個數據不屬於連接的範疇。我們之所以說UDP是無連接的,就是因爲它只有這四個數據,而沒有通信雙方的一些初始化數據,無腦地給對方扔東西;TCP是面向連接的,就是因爲它除了這四個數據,還有通信雙方的一些初始化數據,有規定地給對方扔東西)所以建立連接就是指通信雙方交換彼此的初始化數據,斷開連接就是指通信雙方清空彼此初始化數據。

爲什麼要建立連接?爲什麼要釋放連接?因爲保證可靠傳輸那三套機制必須得有一堆初始化數據才能搞,所以我們必須在真正傳輸數據前先建立連接交換彼此的數據,否則沒辦法做可靠傳輸。反過來,當真正的數據傳輸完了,就清空一下彼此的數據,一方面可以節省內存空間,二方面客戶端的端口號是隨機的,下次萬一這個客戶端又隨機到這個的端口號(這就意味着源端口號、目標端口號、源IP、目標IP這四個數據可能跟上次全部一樣),那大家要是基於上次的數據做數據傳輸就會出錯了。

怎麼建立連接的?三次握手建立連接。

假設客戶端要向服務器發一個HTTP請求。

一開始客戶端處於CLOSED關閉狀態,服務器處於LISTEN監聽狀態。

當客戶端要給服務器發送數據的時候,客戶端就會發起第一次握手——即發一個“SYN = 1 + 客戶端的初始化數據”的TCP數據段給服務器(SYN = 1,代表一個希望跟你建立連接的請求),發出後客戶端立即進入SYN-SENT連接請求已發送狀態,此時服務器處於LISTEN監聽狀態。

當服務器收到第一次握手後,就會發起第二次握手——即回覆一個“ACK = 1、SYN = 1 + 服務器的初始化數據”的TCP數據段給客戶端(ACK = 1,代表我收到了你的消息、給你個回覆;SYN = 1,代表一個希望跟你建立連接的請求),發出後服務器立即進入SYN-RCVD連接請求已接收狀態,此時客戶端處於SYN-SENT同步已發送狀態。

當客戶端收到第二次握手後,就會發起第三次握手——即回覆一個“ACK = 1”的TCP數據段給服務器(ACK = 1,代表我收到了你的消息、給你個回覆,發出後客戶端立即進入ESTABLISHED連接已建立狀態,此時服務器處於SYN-RCVD連接請求已接收狀態。

當服務器收到第三次握手後,立即進入ESTABLISHED連接已建立狀態,此時客戶端處於ESTABLISHED連接已建立狀態,連接建立成功。

客戶端就可以給服務端發送數據了。

疑問:爲什麼非要三次握手?既然建立連接就是指通信雙方交換彼此的初始化數據,那兩次握手就夠了呀,客戶端通過第一次握手把自己的初始化數據傳遞給服務器,服務器通過第二次握手把自己的初始化數據傳遞給客戶端,通信雙方就都有了彼此的初始化數據,就能夠保證可靠傳輸了呀,那爲什麼還要第三次握手呢?通俗地回答就是“讓服務器知道客戶端已經收到了它的初始化數據”,那我們就來分析一下這個通俗的回答到底站不站得住腳,假設根本不存在第三次握手——也就是說服務器只是把初始化數據發出去了、但是根本不知道客戶端到底收沒收到它的初始化數據,正常情況下是客戶端收到了服務器的初始化數據,那雙方就是正常地傳輸數據;異常情況下是客戶端沒收到服務器的初始化數據——這不就等價於是客戶端沒收到服務器針對第一次握手的回覆嘛,那過了超時時間客戶端肯定會發起超時重傳,直到收到服務器的初始化數據,如果重傳N次還不行,那就斷開連接,伺機再連接,所以說兩次握手肯定就能保證客戶端和服務器都收到對方的初始化數據,因爲有超時重傳機制存在啊。所以“服務器知不知道客戶端收到了它的初始化數據”根本無所謂,它知道了也沒有任何意義,它不知道也不會造成任何影響,所以這個通俗的回答有待商榷。那從專業知識的角度回答一下:假設沒有第三次握手,客戶端發起了第一次握手(第一次),但是在傳輸過程中這個數據段可能選了個較遠的路徑或者網不好,遲遲沒到達服務器,服務器就沒法給客戶端回覆了,過了超時時間還是沒收到,那客戶端就會認爲第一次握手(第一次)出問題了,於是重新發起第一次握手(第二次),第二次握手順利到達服務器,服務器也順利發起了第二握手,接着雙方傳輸數據完畢,四次揮手斷開了連接,可就在連接都斷開了,很可能客戶端發起的第一次握手(第一次)纔到達服務器,那此時服務器會認爲是個有效的連接請求,於是給客戶端發了一個第二次握手的回覆,客戶端收到第二次握手的回覆後,發現我早就跟你傳輸完數據了,我不想建立連接了啊,於是就不搭理服務器,這樣服務器就會白白分配資源監聽客戶端了,造成服務器資源的浪費。但是如果加上第三次握手——即服務器會在收到第三次握手後纔會分配資源監聽客戶端,那當饒了很大一圈的第一次握手(第一次)到達服務器,服務器雖然依舊會認爲是個有效的連接請求,於是給客戶端發了一個第二次握手的回覆,客戶端收到第二次握手的回覆後,發現我早就跟你傳輸完數據了,我不想建立連接了啊,於是就不搭理服務器,於是服務器就不會收到第三次握手,於是服務器就不會分配資源監聽客戶端了,從而避免了資源浪費。

怎麼斷開連接的?四次揮手斷開連接。

TCP是全雙工通信。

假設客戶端發現沒什麼數據要發給服務器了,想要主動斷開連接,那客戶端就會發起第一次揮手——即給服務器發送一個“FIN = 1”的數據段(FIN = 1,代表一個希望跟你斷開連接的請求),代表客戶端告訴服務器我已經沒有什麼東西要發給你了,服務器收到第一次揮手後就會發起第二次揮手——即給客戶端發送一個“ACK = 1”的數據段(ACK = 1,代表我收到了你的消息、給你個回覆),代表服務器已經知道客戶端沒有什麼東西要發給我了,但是此時還不能斷開連接,因爲這一個回合下來只代表客戶端給服務器發數據這個方向的通道可以關閉了,服務器可還有可能想給客戶端傳數據呢,所以我們沒看到第二次揮手的數據段裏有“FIN = 1”。

等到了某個時機,服務器也發現沒什麼數據要發給客戶端了,於是發起第三次揮手——即給客戶端發送一個“FIN = 1”的數據段,代表服務器告訴客戶端我已經沒有什麼東西要發給你了,客戶端收到第三次揮手後就會發起第四次揮手——即給服務器發送一個“ACK = 1”的數據段,代表客戶端已經知道服務器沒有什麼東西要發給我了,此時雙向通道就可以都關閉掉了,於是斷開連接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章