掌握《網路》,見微才能知著

蔡不菜丶發表於2022-03-21

大家好呀,我是小菜~

本文主要介紹 網路

如有需要,可以參考! 如有幫助,不忘 點贊

微信公眾號已開啟,菜農曰,沒關注的同學們記得關注哦!

我們先思考一個問題, 什麼是網路?

網路 這個東西在我們的生活中可謂是如影隨形! 但是網路這個詞又太過泛化, 我們最直觀的認識就是開啟瀏覽器, 輸入某個地址, 然後給我們響應出頁面. 既然有了網路, 為了通訊正常, 就出現了各種各樣的 網路協議, 協議的主要作用就是 約束, 只有雙方互相遵守協議, 那麼通訊才能正常的走下去 !

那麼我們平時接觸最多的是什麼協議呢?

有小夥伴就搶答了, Http 協議 ! 又有的小夥伴說是 Https 協議

兩個回答都是正確的, 正常來說可以統稱為 Http 協議, 而帶上鎖的就是 Https 協議, 那麼這兩個協議有什麼不同呢? 這就是我們這篇要講到的內容

HTTP

HTTP ( Hypertext Transfer Protocol ), 超文字傳輸協議

一. 歷程回顧

看到這個詞莫名有種親切感, 因為它貼近我們的生活, 我們可以回顧它的成長曆程

  • 1991 年

    它誕生了, 當時名為 HTTP 0.9, 這個版本極其簡單, 只有一個 Get 命令, 也就是隻能獲取資源

  • 1996 年

經過 5 年的時間, 孵化了 1.0 這個版本, 也就是 HTTP 1.0, 這個版本有了很大的升級, 以至於現在大多數人認為 1.0 是它的初始版本, 而不知道 0.9 這個版本的存在. 這個版本相對成熟, 任何格式的內容都可以傳送, 這使得網際網路不僅可以傳輸文字,還能傳輸影像、視訊、二進位制檔案, 這為網際網路的大發展奠定了基礎 !

  • 1999 年

HTTP 1.1 版本隨之釋出. Http 1.0 相對成熟, 但有缺陷. 而 1.1 版本在繼承了HTTP 1.0優點的基礎上,也克服了HTTP 1.0的效能問題

  • 2015 年

HTTP/2 出現了, 這次的發版不再有 .x 的版本形式, 也說明了這次發版的自信與實力. 但目前這個版本還比較新, 還沒達到普及的程度

二. HTTP1.0

HTTP 協議有什麼特點?

最基本的一個特點就是 一來一回, 它是在服務端收到請求後才會做出回應. 也就是說客戶端在獲取資源的時候會發起一個 TCP 連線, 在連線上發一個 HTTP Request 到伺服器, 然後伺服器才會返回一個 HTTP Response 做出響應, 那這樣就會每來一個請求, 就會開啟一個連線

那麼問題來了, 這樣子會有什麼問題 ? 答案是肯定的, 明顯的問題如下:

  • 效能問題

連線的建立和關閉都是耗時操作, 而我們現在開啟一個網頁, 載入了幾十個資源, 那如果沒請求一次就要開一個 TCP 連線, 那是非常耗時的. 有小夥伴就說了, 現在都流行併發了, 可以開多個連線, 併發傳送呀 ! 是的, 的確可以併發傳送請求, 但是我們還需要明白一點, 連線數不是無限的 !

  • 推送問題

我們在上面說到了服務端是被動響應的, 也就是服務端沒有辦法在客戶端沒有請求的情況下主動向客戶端推送訊息. 當然這個問題放到現在也不算什麼了, 已經有了很多解決方案!

我們先來認識 HTTP1.0 這個版本, 0.9 實在有太多讓人詬病的地方, 因此不算完美的它出現了改進版也就是 1.0 版本. 這個版本給我們帶了兩個特性

1. Keep-Alive 機制

眾所周知, 效能問題是很嚴重的問題, 頻繁的連線建立斷開嚴重損耗了效能. 我們完全可以想象獲取每個資源都需要一個連線, 而且一個連線用完就立刻關閉, 這多少有點奢侈揮霍了 ~

因此為了解決這個問題, HTTP 1.0 帶來了 Keep-Alive 機制, 它可以實現 TCP 連線的複用

這個機制如何體現呢?

客戶端只需要在請求的頭部加上一個標識: Connection: Keep-Alive, 當服務端收到來自客戶端的屬性後, 看到該請求的頭部攜帶了 Keep-Alive 標識, 便不會在請求處理結束後關閉該連線, 同時在 HTTP 的響應頭中也會加上該欄位, 告訴客戶端這個請求沒有關閉

但是我們需要知道 TCP 連線數是有限的, 如果每個資源請求過來都想保持連線存活, 這也是一件不現實的事情, 因此, 服務端會有一個 keep-Alive timeout引數, 過一段時間之後, 如果該連線上沒有新的請求進來, 連線就會關閉

2. Content-Length 屬性

這個屬性與上面那個機制息息相關, 之前的處理方式都是一個連線一個請求響應一類資源, 當資源返回後客戶端就知道這個連線直接關閉了. 當有了 Keep-Alive 機制後, 客戶端就不知道這個請求什麼時候關閉了, 一個請求處理完了,連線也不關閉,那麼客戶端怎麼知道連線處理結束了呢?或者說,客戶端怎麼知道接收回來的資料包是完整的呢?

因此就有了 Content-Length 屬性, 這個欄位可以告訴客戶端 HTTP Response 的 Body 有多少個位元組, 那麼客戶端接收到多少個位元組之後就知道響應成功了

三. HTTP 1.1

每一次的版本更新都是為了填上個版本留下的坑

上圖是 1.0 版本帶來的解決問題, 可以說解決效能問題, 連線複用是很有必要的. 因此到了 HTTP1.1 連線複用就變成了一個預設屬性, 即使不加上 Connection: Keep-Alive 屬性, 伺服器也會在請求處理完畢之後不關閉連線. 除非顯式的加上 Connection: close

為了讓客戶端判斷一個請求的響應是否成功, 1.0 也煞費苦心引入了 Content-Length 屬性, 那麼問題就來了, 如果服務端返回的內容過長且複雜, 那麼 Content-Length 的計算是不是也變的尤為耗時? 而且客戶端也為了比對, 也需要計算 Content-Length,

1. Chunk 機制

因此 HTTP 1.1 為了解決這個問題, 引入了 Chunk 機制 (Http Streaming), 也就是服務端會在響應頭加上 Transfer-Encoding: chunked 屬性, 這樣是為了告訴客戶端, 響應的 Body 是分成了一塊塊, 每塊之間都有分割符, 所有塊的結尾也有個特殊標記, 因此這樣客戶端也可以很快的判斷出響應的末尾

2. Pipeline 與 Head-of-line Blocking

當我們引入了連線複用後, 感覺還是很慢是怎麼回事?

因為這個時候還存在一個問題, 請求是序列的, 客戶端傳送一個請求, 收到響應後才會繼續傳送下一個請求

擅長寫程式碼的我們都習慣一言不合就多執行緒, 一個執行緒不夠就兩個, 就是要提高併發度, 只有併發度上來了, 效能才能提升 ! 當然這個理論上的定義, 具體實現在開發中不一定多執行緒就是好

HTTP 1.1 也因此引入了 Piepline 機制, 在同一個 TCP 連線上面, 可以在一個請求發出去之後, 響應沒有回來之前, 就可以再傳送下一個請求, 這樣提高了併發度, 也提高了效能 ~

但凡事都有雙面性, Piepline 機制 也有個致命的問題, 那就是 Head-of-Line Blocking. 大白話翻譯過來就是 隊頭阻塞

客戶端傳送的請求是 1, 2, 3, 三次請求在服務端上可以併發處理. 但是客戶端也有自己的脾氣, 接受響應的順序也必須是 1, 2, 3, 一旦請求 1 發生了延遲, 則請求2, 3也會被阻塞. 因此為了避免 Piepline 帶來的副作用, 很多瀏覽器預設就是把該機制關閉了

那麼既然 Piepline 這條路行不通, 有沒有其他的妙招? 當然 ! 你永遠可以相信前輩的奇思妙想 ~

3. 腦洞風暴
在瀏覽器中, 對於同一個域名, 只能開 6~8 個連線, 但一個網頁需要傳送幾十個HTTP請求, 那麼 6~8 個連線是遠遠不夠用的
1. Spriting 技術

這種技術是針對小圖片的, 既然限制了連線數, 那麼就可以很多歌小圖片拼成一個大圖片, 到了瀏覽器後, 再通過 JS 或 CSS , 從大圖中擷取一小塊顯示, 而這個可以將之前需要發很多個請求的問題, 變成只發一個請求就可以解決了

2. Inlining 內聯

內聯是另外一種針對圖片載入的技術, 它直接將圖片的 Base64 插入 CSS 檔案中, 就避免了載入

3. JS 拼接

把大量小的 JS 檔案合併成一個檔案並壓縮, 讓瀏覽器在一次請求中可以直接載入完

4. 請求的分片技術

既然一個域名只能建立 6~8次連線, 那豈不是多整幾個域名, 就可以提升連線數了, 這就相當於繞開了瀏覽器的限制.

4. 斷點續傳

HTTP 1.1 還有個好用的功能就是 斷點續傳, 當客戶端從伺服器下載檔案的時候, 如果下載到一半連線中斷了, 再新建連線之後客戶端還是可以從上次斷的地方繼續下載.

這是因為客戶端在下載的時候, 一邊下載一邊記錄下載的資料量大小, 一旦連線中斷了, 就可以在請求的頭部加上 Range: first offset-last offset欄位, 指定從某個 offset 下載

但這種特性只適用於斷點下載, 斷點上傳需要自己實現

四. HTTP/2

每一次的版本更新都是為了填上個版本留下的坑

到目前為止我們從HTTP0.9認識到了HTTP1.1, 在 HTTP/2 出現之前, 1.1 也陪伴了我們長達六年

HTTP1.1 的時候由於 Piepline 導致的隊頭阻塞問題, 而大家集思廣益, 出現了各種各樣的解決方案. 但有沒有發現這些解決方案都是從 應用層面 去解決的, 沒有普適性, 因此想要通用, 還是得從 協議 層面解決. 因此基於這個方向, 出現了 HTTP/2, 大家也可以發現這個版本並沒有稱之為 2.0, 可能是因為工作組認為該協議已經足夠完善, 不會再有小版本進行修復, 如果有的話也就是 HTTP/3 的事情了.

1. 二進位制分幀

隊頭阻塞的問題是一個難題, 哪怕是 HTTP/2 也沒有解決

二進位制分幀 是 HTTP/2 為了解決 隊頭阻塞 問題設計的核心特性, HTTP 1.1本身是明文的字元格式,而二進位制分幀,是指在把這個字元格式的報文給TCP之前轉換成二進位制,並且分成多個幀(多個資料塊)來傳送。

圖源網, 侵刪

在 HTTP/2 連線資料傳送的過程中, 請求和響應都是被打散後分成多個幀亂序地發出去的, 請求和響應都需要重新組裝起來, 同時請求和響應還要一一配對, 那麼問題又隨之而來了.

組裝和配對如何實現 ?

其實這邊解決的原理也很簡單, 每個請求和響應實際上組成了一個邏輯上的 , 為每條流分配一個流 ID, 把這個ID作為每個流的標籤.

這種實現方式有沒有讓你想到什麼? 是不是類似我們上面說到的資料包編號 ? 沒錯, 所以說 HTTP/2 並沒有完全解決 隊頭阻塞 問題, 而是把 隊頭阻塞 問題從HTTP Request粒度細化到了“幀”粒度, 儘管沒有解決, 但是基於 幀 的粒度可以降低隊頭阻塞發生的可能性, 同樣提高了效能 !

圖源網, 侵刪

為什麼 HTTP/2 還是沒能解決隊頭阻塞的問題呢?

這個問題我們需要從源頭分析, 這裡就直接給出結論了: 只要用 TCP 協議,就繞不開“隊頭阻塞”問題,因為 TCP 協議是先進先出的!

2. 頭部壓縮

除了二進位制分幀,HTTP/2 另外一個提升效率的方法就是使用頭部壓縮

在HTTP 1.1裡,對於報文的報文體,已經有相應的壓縮,尤其對於圖片,本來就是壓縮過的. 但對於報文的頭部,一直沒有做過壓縮。

在網際網路野蠻生長的時代, 應用場景越來越複雜, 很多時候都會將引數放到請求頭中, 這就會導致報文的頭部變得很大, 這個時候對頭部做壓縮就變得很有必要. 因此 HTTP/2 專門設計了一個 HPACK 協議和對應的演算法. 這種方式同樣也能夠過提高傳輸的速度

六. HTTPS

網際網路充滿了危險, 而有危險的地方就需要安全的保證, 因此它來了 - HTTPS

1. SSL/TLS

在正式介紹 HTTPS 之前, 我們很有必要先認識下 SSL/TLS

  • SSL: Secure Sockets Layer, 安全套接層
  • TLS: Transport Layer Security, 傳輸層安全協議
小菜曾經認為這兩個概念是在 HTTPS 提出之後才引入的, 現在想想難免有些 荒唐, 不知道有多少夥計跟小菜想的一樣

既然引入時間不詳, 那就很有必要做一個歷史回顧了

SSL/TLS 出現的時間很早, 有多早? 幾乎和網際網路歷史一樣長

  • 1994年: 網景公司設計了 SSL 1.0
  • 1995年: 網景公司釋出了 SSL 2.0, 但很快發現存在嚴重漏洞
  • 1996年: SSL 3.0 釋出, 得到大規模應用
  • 1999年: 網際網路標準化組織 IETF 對 SSL 進行標準化, 釋出了 TLS 1.0
  • 2006年和2008年: TLS 進行了兩次升級, 分別為 TLS 1.1 和TLS 1.2

那麼在網路層中 , SSL/TLS 是處於什麼位置呢? 我們可以看下圖:

它是位於應用層與傳輸層之間, 不僅可以支撐 HTTP 協議, 還可以支援 FTP, IMTP 等其他各種協議

SSL/TLS 主要是用於保證安全傳輸的, 如何保證安全傳輸? 那就需要使用加密了, 我們先來了解幾種加密模式

  • 對稱加密

對稱加密實現很簡單, 就是雙方共同持有一個金鑰, 來往訊息都採用同一個金鑰加解密即可

但是問題就來了, 客戶端與服務端雙方還未進行約定之前, 如何通知雙方金鑰是什麼?

金鑰A 通過 金鑰B 加密, 金鑰B 再通過 金鑰C 加密? 這豈不是套娃嗎 !

因此這種加密方式不適用 !

  • 非對稱加密

客戶端為自己準備一對公私鑰 ( PubA,PriA ), 伺服器為自己準備一對公私鑰 ( PubB,PriB ). 公私鑰有個關鍵特性:公鑰PubA是通過私鑰PriA 計算出來的,但反過來不行,不能根據PubA推算出PriA

通過這種方式雙方只需要將自己的公鑰公開出去就行, 自己保留就行.

簽名 的目的就相當於蓋章, 說明這段訊息是你傳送出來的, 具有不可抵賴性質.

這種方式挺合理的, 但是同樣會遇到一個問題, 那就是公鑰如何安全傳輸?

也就經典的中間人攻擊, 在傳輸公鑰的時候中間人可以做攔截, 將雙方的公鑰換成中間人的公鑰, 那麼接下來的通訊相當於都在中間的掌控之中

那麼就需要想個辦法證明伺服器收到的公鑰, 的確就是客戶端發出的, 中間沒有人可以篡改這個公鑰,反過來也是一樣.

  • 數字證書與證書認證中心

為了解決這個問題, 我們急需一個公證處 CA , 在公證處保管著公鑰, 凡是在公證處有儲存過公鑰的, 公證處都會頒發一個數字證書 ( Certficate ), 這個證書就相當於是一張身份證. 之後的傳輸雙方只需要傳輸證書, 然後拿著證書去公證處核定是否真實

那麼這個時候就又會出現一個問題,CA是假的怎麼辦, 中間人也有可能充當 CA 這個角色, 雙方高高興興的通訊半天, 到頭來發現是跟中間人通訊, 這不得一口老血吐出來

而這個時候就需要給 CA 頒發證書, 那麼 CA 的證書又該由誰來頒發呢, 那就是 CA 的上一級 CA, 這就是證書信任鏈

這邊涉及到兩個過程:

  • 驗證過程
  1. 客戶端要驗證伺服器的合法性,需要拿著伺服器的證書C3,到CA2處去驗證
  2. 客戶端要驗證CA2的合法性,需要拿著CA2的證書C2,到CA1處去驗證
  3. 客戶端要驗證CA1的合法性,需要拿著CA1的證書C1,到CA0處去驗證

那麼其中 CA0 如何認證呢 ? 對於它我們只能無條件信任. 他就說傳說的 根證書, Root CA 機構都是一些世界上公認的機構,

  • 頒發過程

頒發過程也是類似的, 上一級CA給下一級CA頒發證書。從根CA(CA0)開始,CA0給CA1頒發證書,CA1給CA2頒發證書,CA2給應用伺服器頒發證書

在我們瞭解了以上概念後, 再來看 SSL/TLS 就不難了,

在建立TCP連線之後、資料傳送之前,SSL/TLS協議會通過四次握手, 來協商出客戶端和伺服器之間的對稱加密金鑰, 然後之後的連線都會帶上這個金鑰

2. HTTPS

那麼什麼是 HTTPS, 現在我們只要記住一個公式就可以瞭解了, 那就是 : HTTPS = HTTP + SSL/TLS

我們到這裡已經掌握了 HTTP, 也掌握了 SSL/TLS, 也就間接掌握了 HTTPS~

因此, 到了 HTTPS 這裡傳輸過程就分成了三個階段:

  • 階段一: TCP 連線的建立
  • 階段二: SSL/TLS 四次握手協商出對稱加密的金鑰
  • 階段三: 基於金鑰, 在TCP連線上對所有的 HTTP Req/Res 進行加解密傳輸

有些夥計就提問了, TCP 連線建立就已經很耗時了, 現在還要加上 SSL/TLS 的握手, 那 HTTPS 的速度豈不是很慢?

其實不然, 在傳輸過程中階段一和階段二隻在連線建立的時候做一次, 之後只要連線不關閉, 每個請求都只需要經過階段三, 因此效能並沒有太大影響 !

五. TCP/UDP

既然說到了 HTTP, 那麼 TCP 和 UDP 是繞不開的, 我們一塊講講 ~

為什麼繞不開呢, HTTP 和 TCP 之間有什麼關係?

HTTP是要基於TCP連線基礎上的,簡單的說,TCP就是單純建立連線,不涉及任何我們需要請求的實際資料,簡單的傳輸。HTTP是用來收發資料,即實際應用上來的。

我們可以總結三個點:

  • TCP是底層通訊協議,定義的是資料傳輸和連線方式的規範
  • HTTP是應用層協議,定義的是傳輸資料的內容的規範
  • HTTP協議中的資料是利用TCP協議傳輸的,所以支援HTTP也就一定支援TCP

而要講 TCP 自然會帶上 UDP 做比較 , 這就是一對親兄弟, 我們下來分別從幾個特性來介紹 TCP 和 UDP 的區別

可靠性

要問你對 TCP 和 UDP 的認識, 大多數人都會順口就來, 一個是可靠連線, 一個是不可靠連線 ! 那小菜就要提出幾個問題了

  • 哪個是可靠連線, 哪個是不可靠連線
  • 可靠連線的定義是什麼
  • 可靠連線是如何保證可靠的

第一個問題很簡單, 絕大部分同學都可以輕鬆回答: TCP 是可靠連線, UDP 是不可靠連線.

到了第二個問題可能就有點犯怵了

"可靠? 就是值得信賴是嗎?"

值得信賴是對的, 但是信賴太過泛化了, 為什麼值得信賴, 有哪些值得信賴的地方?

這裡給出三點定義:

  • 資料包不丟
  • 資料包不重
  • 時序不亂

只有符合以上三點, 才算是可靠連線, 而 TCP 正好符合 ~

那麼繼續我們的第三個問題, 如何保證可靠 ? 小菜這裡也就不賣關子了, 我們們接著往下看

首先是 資料包不丟

1. 解決不丟問題 : ACK + 重發

網路中丟包是一種很正常的現象, 丟了怎麼辦? 重發唄 ! 就是這麼簡單暴力, 伺服器每次收到一個包, 就會對客戶端進行確認, 反饋給客戶端收到包的訊號, 也就是 ACK 訊號, 而如果客戶端在指定的時間內沒有收到 ACK 確認訊號, 就會再次重發資料.

但是 ACK 訊號是收到一個包, 就傳送一個確認嗎? 不是的 ! 這種效率實在太低了, 那麼解決方法就是: 客戶端會對傳送的每個資料包進行編號, 編號由小到大依次遞增, 那麼只有基於編號就能確定好順序

就好比如服務端收到了包 1, 2, 3. 那麼它只需要回覆客戶端 ACK=3, 意思是所有小於或等於 3 的資料包都已經收到了, 接著繼續收到包 4, 5, 6. 而這時就需要回復 ACK=7, 意思就是所有小於或等於7的資料包都已經收到了

2. 解決不重問題 : ACK 判定

上面說到客戶端在指定的時間內沒有收到 ACK 確認訊號, 就會再次重發資料. 但是超時不代表服務端沒收到, 有可能是因為網路延遲, 傳送的資料包或 ACK 還在路上, 實際上是收到了, 而這時如果客戶端重發資料, 就會導致重複訊息, 這個時候就需要判重

那麼如何判重? 這也可以使用 ACK , 當服務端給客戶端回覆 ACK=6, 意味著所有小於或等於 6 的資料包都已經收到了, 而這個時候如果再收到這個範圍的資料包, 服務端就可以直接丟棄不做處理

3. 解決時序錯亂問題

網路是會有延遲的, 而且資料包是通過網路中不同節點傳送的, 每個節點傳送的速率都是不一樣的, 就有可能客戶端傳送了 4, 5, 6 三個包, 這個時候 5, 6 兩個包先到, 而 4 號包還在路上, 這個時候就容易出現亂序的問題

而服務端處理的方式也很巧妙, 它會將 5, 6兩個包暫時存放, 直到 4 號包的到來, 再給客戶端回覆 ACK=7, 如果 4 號包不來, 服務端也不會做 ACK 回覆, 那麼客戶端等到超過指定時間又會重新傳送 4, 5, 6 三個包

因此通過訊息順序編號+客戶端重發+伺服器順序ACK,實現了客戶端到伺服器的資料包的不丟、不重、時序不亂

三次握手

TCP 的 三次握手/四次揮手 實在是個老生常談的問題了, 小菜甚至不願多花費篇幅來解釋, 但為了保證內容的完整性, 我們們本著負責到底的精神繼續做次重溫 !

我們通過圖可以看到有兩個比較關鍵的key/value, 一個是 seq=x, 一個是 ACK=x+1

  • seq: 上面說到了客戶端往服務端傳送訊息, 都會將每個資料包進行編號且排序, 而這個 seq 便是表示發出去的包的編號是 x, 因為 TCP 是全雙工的, 所以通訊雙方一方面要傳送自己編號的包, 一方面要向對方確認收到的包, 因此為了優化傳輸, 會把兩個包合在一起傳輸, 所以就有了在同一個包裡既包含 seq, 也包含 ACK
  • ACK: ACK 這個詞在上面也已經介紹過了, 表示告訴對方我已經收到編號小於或等於 x 的包了, 而在這裡就是表示小於或等於 x 的包都已經收到了, 接下來要準備接收 x+1 的包
為什麼要三次握手, 兩次不行嗎? 四次不行嗎?

這是一道經典面試題 ~ 這裡直接給出肯定的回答: 兩次不行, 四次可以但沒必要

通訊之前我們需要確認雙方都要具備 收發 能力, 才有進行下一步的必要, 不然就是扯淡 ! 舉個通俗的例子

"喂, 你愛我嗎"

"我愛你呀, 你愛我嗎"

"我也愛你"

戀愛三部曲, 首先要確認雙方是否相愛, 如果不確認是否相愛, 那有進行談戀愛的必要嗎

如果變成了兩次握手

"喂, 你愛我嗎"

"我愛你呀, 你愛我嗎"

兩次握手無法確定雙發是否相愛, 那麼戀情就無法繼續下去, 就容易發展誤會, 最後不了了之

那麼四次握手呢? 既然三次握手就可以確認雙發是否相愛, 四次握手肯定也行, 但卻沒必要

因此三次握手恰好可以保證客戶端和伺服器對自己的傳送、接收能力做了一次確認。第一次,客戶端給伺服器發了seq=x,無法得到對方是否收到;第二次,對方回覆了seq=y,ACK=x+1。這時客戶端知道自己的傳送和接收能力沒有問題,但伺服器只知道自己的接收能力沒問題;第三次,客戶端傳送了ACK=y+1,伺服器收到後知道自己第二次發的ACK對方收到了,傳送能力也沒問題

四次揮手

相比於建立連線的三次握手,關閉連線的四次揮手會顯得更加複雜.

四次揮手也得因於 TCP 是全雙工的, 有個 Half-Close 狀態, 我們可以來模擬對話

客戶端: "Hi, 伺服器, 我要關閉連線了"

伺服器: "哦好的, 我知道了, 等我下"

伺服器: "我好了, 關閉連線吧"

客戶端: "好的"

這裡可以思考下, 為什麼不進行三次揮手呢?

客戶端: "Hi, 伺服器, 我要關閉連線了"

伺服器: "哦好的, 我知道了, 關閉連線吧"

客戶端: "好的"

感覺三次揮手也沒毛病, 四次揮手中的 等我下 是等什麼呢? 這裡我們需要重點關注下


連線是邏輯上的連線, 也就是說 連線是假的, 並不想我們現實中的物理連線, 具有實體意義. 那麼如果直接進行了三次揮手, 就會導致一些還在路上的資料包 迷路閒逛. 那麼問題就來了, 如果連線斷了就斷了, 這些資料包直接丟棄即可, 但是連線可能重連, 這就會導致之前閒逛的資料包在新連線開啟後被當做新的資料包, 就可能出現錯亂的問題


解決:

在 TCP/IP 網路中, 有個值叫做 MSL ( Maximum Segment Lifetime ), 任何一個IP資料包在網路上逗留的最長時間是 MSL. 這個值預設是 120 s, 意味著一個資料包必須在 MSL 時間範圍內到達目的地, 如果超出了這個時間, 中間的路由節點就會將這個資料包丟棄. 因此可以基於限定, 服務端在知道要關閉連線的時候可以等它個 2*MSL 時間, 然後進入關閉階段.

結論:

一個連線並不是想關就能立刻關的,關閉後還要等2*MSL時間才能重開。

考驗你有沒有做黑客的時候到了 ~ 這會導致什麼問題呢?

如果頻繁地建立連線,最後可能導致大量的連線處於TIME_WAIT狀態,最終耗光所有的連線資源。

因此我們需要採取以下措施

  • 不要讓伺服器主動關閉連線。這樣伺服器的連線就不會處於TIME_WAIT狀態。
  • 客戶端做連線池,複用連線,而不要頻繁地建立和關閉,這其實也是HTTP 1.1和HTTP/2採用的思路。

六. QUIC

HTTP 2.0 已經給全球資訊網帶來了新的特性和速度的提高. 但是作為開發者來說, 不滿足是不斷提升的動力來源 !

QUIC 是一個全新的概念, 它在效能和安全性上又有了一個重大的飛躍, 它可以取代 HTTP/2, 成為 HTTP/3 ! 主要帶來了以下幾個改進

1. 大大減少了連線建立時間

我們在上面已經瞭解到了要建立一個 HTTPS 連線需要七次握手 ( TCP 三次握手 + SSL/TLS 四次握手 ), 想要較少連線建立的時間, 那麼就得從握手上面入手, 也就是減少 RTT 的次數. 而對於 QUIC 協議, 可以把前面的七次握手, 減為 0 次!

圖源網, 侵刪

2. 無隊頭阻塞的多路複用

我們重新認識下什麼是 隊頭阻塞, HTTP/2

只要使用 TCP,就沒有辦法完全解決隊頭阻塞問題,因為 TCP 是先傳送先接收,而 UDP 沒有這個限制

那麼 QUIC 解決了無隊頭阻塞問題, 我們就可以很容易的想到因果關係, 那就是 QUIC 是基於 UDP 構建的. 在 QUIC 中丟失一個資料包不會減慢包中其他請求的速度

3. 連線遷移

TCP 的連線是由 4元組 [ 源地址, 源埠, 目標地址, 目標埠 ] 組成的, 在移動端上就會有個問題, 如果客戶端是 WIFI 或 4G, 客戶端的 IP 是一直在變的, 這會導致什麼問題呢? 沒錯, 這意味著頻繁地建立和關閉連線, 最直觀的感受就是, 我們平時看視訊的時候如果 WIFI 訊號不好, 我們想要切換到 4G 播放的時候, 會等待一會, 然後才能繼續播放

那麼解決的想法也很簡單, 就是在客戶端 IP 和 埠 浮動的情況下, 連線仍然可以保持 !

TCP 的連線本來就是假的, 它是一個邏輯上的概念, 既然是邏輯上的概念, QUIC 就可以創造一個邏輯上的連線, 那麼就不能以 4元組 的方式來標識連線, 因為它會變, 而是讓客戶端生成一個 64 位的數字標識連線, 只要這個數字標識不變, 任管 IP 和 埠 如何漂移, 這條連線就會一直存在, 這對於我們的客觀感受就是 連線一直存在, 從未中斷過

說的花裡胡哨的, 那麼它究竟有多快? 下面放一張 Google 啟用 QUIC 後的趨勢圖

好了, 夥計們, 到這裡我們就已經全面的介紹了網路的概述, 這篇文章並不是深入挖掘的型別, 目的只是讓你籠統的認識網路, 畢竟如果沒有認識, 就要深入, 那麼結局也不會很好, 你說是吧 !

不要空談,不要貪懶,和小菜一起做個吹著牛X做架構的程式猿吧~點個關注做個伴,讓小菜不再孤單。我們們下文見!

今天的你多努力一點,明天的你就能少說一句求人的話!

我是小菜,一個和你一起變強的男人。 ?

微信公眾號已開啟,菜農曰,沒關注的同學們記得關注哦!

相關文章