網路知識#
UDP#
面向報文
UDP 是一個面向報文(報文可以理解為一段段的數據)的協議。意思就是 UDP 只是報文的搬運工,不會對報文進行任何拆分和拼接操作。
- 在發送端,應用層將數據傳遞給傳輸層的 UDP 協議,UDP 只會給數據增加一個 UDP 頭標識下是 UDP 協議,然後就傳遞給網路層了
- 在接收端,網路層將數據傳遞給傳輸層,UDP 只去除 IP 報文頭就傳遞給應用層,不會任何拼接操作
不可靠性
- UDP 是無連接的,也就是說通信不需要建立和斷開連接。
- UDP 也是不可靠的。協議收到什麼數據就傳遞什麼數據,並且也不會備份數據,對方能不能收到是不關心的
- UDP 沒有擁塞控制,一直會以恆定的速度發送數據。即使網路條件不好,也不會對發送速率進行調整。這樣實現的弊端就是在網路條件不好的情況下可能會導致丟包,但是優點也很明顯,在某些實時性要求高的場景(比如電話會議)就需要使用 UDP 而不是 TCP。
高效
因為 UDP 沒有 TCP 那麼複雜,需要保證數據不丟失且有序到達。所以 UDP 的頭部開銷小,只有八字節,相比 TCP 的至少二十字節要少得多,在傳輸數據報文時是很高效的。
頭部包含了以下幾個數據
- 兩個十六位的端口號,分別為源端口(可選字段)和目標端口
- 整個數據報文的長度
- 整個數據報文的檢驗和(IPv4 可選 字段),該字段用於發現頭部信息和數據中的錯誤
傳輸方式
UDP 不止支持一對一的傳輸方式,同樣支持一對多,多對多,多對一的方式,也就是說 UDP 提供了單播,多播,廣播的功能。
TCP#
對於 TCP 頭部來說,以下幾個字段是很重要的
- Sequence number,這個序號保證了 TCP 傳輸的報文都是有序的,對端可以通過序號順序的拼接報文
- Acknowledgement Number,這個序號表示數據接收端期望接收的下一個字節的編號是多少,同時也表示上一個序號的數據已經收到
- Window Size,窗口大小,表示還能接收多少字節的數據,用於流量控制
- 標識符
- URG=1:該字段為一表示本數據報的數據部分包含緊急信息,是一個高優先級數據報,此時緊急指針有效。緊急數據一定位於當前數據包數據部分的最前面,緊急指針標明了緊急數據的尾部。
- ACK=1:該字段為一表示確認號字段有效。此外,TCP 還規定在連接建立後傳送的所有報文段都必須把 ACK 置為一。
- PSH=1:該字段為一表示接收端應該立即將數據 push 給應用層,而不是等到緩衝區滿後再提交。
- RST=1:該字段為一表示當前 TCP 連接出現嚴重問題,可能需要重新建立 TCP 連接,也可以用於拒絕非法的報文段和拒絕連接請求。
- SYN=1:當 SYN=1,ACK=0 時,表示當前報文段是一個連接請求報文。當 SYN=1,ACK=1 時,表示當前報文段是一個同意建立連接的應答報文。
- FIN=1:該字段為一表示此報文段是一個釋放連接的請求報文。
狀態機
HTTP 是無連接的,所以作為下層的 TCP 協議也是無連接的,雖然看似 TCP 將兩端連接了起來,但是其實只是兩端共同維護了一個狀態
TCP 的狀態機是很複雜的,並且與建立斷開連接時的握手息息相關,接下來就來詳細描述下兩種握手。
在這之前需要了解一個重要的性能指標 RTT。該指標表示發送端發送數據到接收到對端數據所需的往返時間。
三次握手
在 TCP 協議中,主動發起請求的一端為客戶端,被動連接的一端稱為服務端。不管是客戶端還是服務端,TCP 連接建立完後都能發送和接收數據,所以 TCP 也是一個全雙工的協議。
起初,兩端都為 CLOSED 狀態。在通信開始前,雙方都會創建 TCB。伺服器創建完 TCB 後便進入 LISTEN 狀態,此時開始等待客戶端發送數據
- 第一次握手
客戶端向服務端發送連接請求報文段。該報文段中包含自身的數據通訊初始序號。請求發送後,客戶端便進入 SYN-SENT 狀態,x 表示客戶端的數據通信初始序號。
- 第二次握手
服務端收到連接請求報文段後,如果同意連接,則會發送一個應答,該應答中也會包含自身的數據通訊初始序號,發送完成後便進入 SYN-RECEIVED 狀態。
- 第三次握手
當客戶端收到連接同意的應答後,還要向服務端發送一個確認報文。客戶端發完這個報文段後便進入 ESTABLISHED 狀態,服務端收到這個應答後也進入 ESTABLISHED 狀態,此時連接建立成功。
PS:第三次握手可以包含數據,通過 TCP 快速打開(TFO)技術。其實只要涉及到握手的協議,都可以使用類似 TFO 的方式,客戶端和服務端存儲相同 cookie,下次握手時發出 cookie 達到減少 RTT 的目的。
四次揮手
TCP 是全雙工的,在斷開連接時兩端都需要發送 FIN 和 ACK。
- 第一次握揮手
若客戶端 A 認為數據發送完成,則它需要向服務端 B 發送連接釋放請求。
- 第二次揮手
B 收到連接釋放請求後,會告訴應用層要釋放 TCP 連接。然後會發送 ACK 包,並進入 CLOSE_WAIT 狀態,表示 A 到 B 的連接已經釋放,不接收 A 發的數據了。但是因為 TCP 連接是雙向的,所以 B 仍舊可以發送數據給 A。
- 第三次握手
B 如果此時還有沒發完的數據會繼續發送,完畢後會向 A 發送連接釋放請求,然後 B 便進入 LAST-ACK 狀態。
PS:通過延遲確認的技術(通常有時間限制,否則對方會誤認為需要重傳),可以將第二次和第三次握手合併,延遲 ACK 包的發送。
- 第四次揮手
A 收到釋放請求後,向 B 發送確認應答,此時 A 進入 TIME-WAIT 狀態。該狀態會持續 2MSL(最大段生存期,指報文段在網路中生存的時間,超時會被拋棄)時間,若該時間段內沒有 B 的重發請求的話,就進入 CLOSED 狀態。當 B 收到確認應答後,也便進入 CLOSED 狀態。
為什麼 A 要進入 TIME-WAIT 狀態,等待 2MSL 時間後才進入 CLOSED 狀態?
為了保證 B 能收到 A 的確認應答。若 A 發完確認應答後直接進入 CLOSED 狀態,如果確認應答因為網路問題一直沒有到達,那麼會造成 B 不能正常關閉。
ARQ 協議#
ARQ 協議也就是超時重傳機制。通過確認和超時機制保證了數據的正確送達,ARQ 協議包含停止等待 ARQ 和連續 ARQ
正常傳輸過程
只要 A 向 B 發送一段報文,都要停止發送並啟動一個定時器,等待對端回應,在定時器時間內接收到對端應答就取消定時器並發送下一段報文。
報文丟失或出錯
在報文傳輸的過程中可能會出現丟包。這時候超過定時器設定的時間就會再次發送丟包的數據直到對端響應,所以需要每次都備份發送的數據。
即使報文正常的傳輸到對端,也可能出現在傳輸過程中報文出錯的問題。這時候對端會拋棄該報文並等待 A 端重傳。
PS:一般定時器設定的時間都會大於一個 RTT 的平均時間。
ACK 超時或丟失
對端傳輸的應答也可能出現丟失或超時的情況。那麼超過定時器時間 A 端照樣會重傳報文。這時候 B 端收到相同序號的報文會丟棄該報文並重傳應答,直到 A 端發送下一個序號的報文。
在超時的情況下也可能出現應答很遲到達,這時 A 端會判斷該序號是否已經接收過,如果接收過只需要丟棄應答即可。
這個協議的缺點就是傳輸效率低,在良好的網路環境下每次發送報文都得等待對端的 ACK。
連續 ARQ#
在連續 ARQ 中,發送端擁有一個發送窗口,可以在沒有收到應答的情況下持續發送窗口內的數據,這樣相比停止等待 ARQ 協議來說減少了等待時間,提高了效率。
累計確認
連續 ARQ 中,接收端會持續不斷收到報文。如果和停止等待 ARQ 中接收一個報文就發送一個應答一樣,就太浪費資源了。通過累計確認,可以在收到多個報文以後統一回覆一個應答報文。報文中的 ACK 可以用來告訴發送端這個序號之前的數據已經全部接收到了,下次請發送這個序號 + 1 的數據。
但是累計確認也有一個弊端。在連續接收報文時,可能會遇到接收到序號 5 的報文後,並未接到序號 6 的報文,然而序號 7 以後的報文已經接收。遇到這種情況時,ACK 只能回覆 6,這樣會造成發送端重複發送數據,這種情況下可以通過 Sack 來解決,這個會在下文說到。
擁塞處理
擁塞處理和流量控制不同,後者是作用於接收方,保證接收方來得及接受數據。而前者是作用於網路,防止過多的數據擁塞網路,避免出現網路負載過大的情況。
擁塞處理包括了四個算法,分別為:慢開始,擁塞避免,快速重傳,快速恢復。
慢開始算法
慢開始算法,顧名思義,就是在傳輸開始時將發送窗口慢慢指數級擴大,從而避免一開始就傳輸大量數據導致網路擁塞。
慢開始算法步驟具體如下
- 連接初始設定擁塞窗口(Congestion Window)為 1 MSS(一個分段的最大數據量)
- 每過一個 RTT 就將窗口大小乘二
- 指數級增長肯定不能沒有限制的,所以有一個閾值限制,當窗口大小大於閾值時就會啟動擁塞避免算法。
擁塞避免算法
擁塞避免算法相比簡單點,每過一個 RTT 窗口大小只加一,這樣能夠避免指數級增長導致網路擁塞,慢慢將大小調整到最佳值。
在傳輸過程中可能定時器超時的情況,這時候 TCP 會認為網路擁塞了,會馬上進行以下步驟:
- 將閾值設為當前擁塞窗口的一半
- 將擁塞窗口設為 1 MSS
- 啟動擁塞避免算法
快速重傳
快速重傳一般和快恢復一起出現。一旦接收端收到的報文出現失序的情況,接收端只會回覆最後一個順序正確的報文序號(沒有 Sack 的情況下)。如果收到三個重複的 ACK,無需等待定時器超時再重發而是啟動快速重傳。具體算法分為兩種:
TCP Taho 實現如下
- 將閾值設為當前擁塞窗口的一半
- 將擁塞窗口設為 1 MSS
- 重新開始慢開始算法
TCP Reno 實現如下
- 擁塞窗口減半
- 將閾值設為當前擁塞窗口
- 進入快恢復階段(重發對端需要的包,一旦收到一個新的 ACK 答復就退出該階段)
- 使用擁塞避免算法
TCP New Ren 改進後的快恢復
TCP New Reno 算法改進了之前 TCP Reno 算法的缺陷。在之前,快恢復中只要收到一個新的 ACK 包,就會退出快恢復。
在 TCP New Reno 中,TCP 發送方先記下三個重複 ACK 的分段的最大序號。
假如我有一個分段數據是 1 ~ 10 這十個序號的報文,其中丟失了序號為 3 和 7 的報文,那麼該分段的最大序號就是 10。發送端只會收到 ACK 序號為 3 的應答。這時候重發序號為 3 的報文,接收方順利接收並會發送 ACK 序號為 7 的應答。這時 TCP 知道對端是有多個包未收到,會繼續發送序號為 7 的報文,接收方順利接收並會發送 ACK 序號為 11 的應答,這時發送端認為這個分段接收端已經順利接收,接下來會退出快恢復階段。
HTTP#
HTTP 協議是個無狀態協議,不會保存狀態。
Post 和 Get 的區別
先引入副作用和幂等的概念。
副作用指對伺服器上的資源做改變,搜索是無副作用的,註冊是副作用的。
幂等指發送 M 和 N 次請求(兩者不相同且都大於 1),伺服器上資源的狀態一致,比如註冊 10 個和 11 個帳號是不幂等的,對文章進行更改 10 次和 11 次是幂等的。
在規範的應用場景上說,Get 多用於無副作用,幂等的場景,例如搜索關鍵字。Post 多用於副作用,不幂等的場景,例如註冊。
在技術上說:
- Get 請求能緩存,Post 不能
- Post 相對 Get 安全一點點,因為 Get 請求都包含在 URL 裡,且會被瀏覽器保存歷史紀錄,Post 不會,但是在抓包的情況下都是一樣的。
- Post 可以通過 request body 來傳輸比 Get 更多的數據,Get 沒有這個技術
- URL 有長度限制,會影響 Get 請求,但是這個長度限制是瀏覽器規定的,不是 RFC 規定的
- Post 支持更多的編碼類型且不對數據類型限制
常見狀態碼
2XX 成功
- 200 OK,表示從客戶端發來的請求在伺服器端被正確處理
- 204 No content,表示請求成功,但響應報文不含實體的主體部分
- 205 Reset Content,表示請求成功,但響應報文不含實體的主體部分,但是與 204 響應不同在於要求請求方重置內容
- 206 Partial Content,進行範圍請求
3XX 重定向
- 301 moved permanently,永久性重定向,表示資源已被分配了新的 URL
- 302 found,臨時性重定向,表示資源臨時被分配了新的 URL
- 303 see other,表示資源存在著另一個 URL,應使用 GET 方法獲取資源
- 304 not modified,表示伺服器允許訪問資源,但因發生請求未滿足條件的情況
- 307 temporary redirect,臨時重定向,和 302 含義類似,但是期望客戶端保持請求方法不變向新的地址發出請求
4XX 客戶端錯誤
- 400 bad request,請求報文存在語法錯誤
- 401 unauthorized,表示發送的請求需要有通過 HTTP 認證的認證信息
- 403 forbidden,表示對請求資源的訪問被伺服器拒絕
- 404 not found,表示在伺服器上沒有找到請求的資源
5XX 伺服器錯誤
- 500 internal sever error,表示伺服器端在執行請求時發生了錯誤
- 501 Not Implemented,表示伺服器不支持當前請求所需要的某個功能
- 503 service unavailable,表明伺服器暫時處於超負載或正在停機維護,無法處理請求
HTTP 首部
通用字段 | 作用 |
---|---|
Cache-Control | 控制緩存行為 |
Connection | 瀏覽器想要優先使用的連接類型,比如 keep-alive |
Date | 創建報文時間 |
Pragma | 報文指令 |
Via | 代理伺服器相關信息 |
Transfer-Encoding | 傳輸編碼方式 |
Warning | 在內容中可能存在錯誤 |
Upgrade | 要求客戶端升級協議 |
響應字段 | 作用 |
---|---|
Accept-Ranges | 是否支持某些種類的範圍 |
Age | 資源在代理緩存中存在的時間 |
ETag | 資源標識 |
Location | 客戶端重定向到某個 URL |
Proxy-Authenticate | 向代理伺服器發送驗證信息 |
Server | 伺服器名字 |
WWW-Authenticate | WWW-Authenticate |
實體字段 | 作用 |
---|---|
Allow | 資源的正確請求方式 |
Content-Encoding | 內容的編碼格式 |
Accept-Encoding | 正確接收的編碼格式列表 |
Content-Language | 內容使用的語言 |
Content-Length | request body 長度 |
Content-Location | 返回數據的備用地址 |
Content-MD5 | Base64 加密格式的內容 MD5 檢驗值 |
Content-Range | 內容的位置範圍 |
Content-Type | 內容的媒體類型 |
Expires | 內容的過期時間 |
Last_modified | 內容的最後修改時間 |
HTTPS#
HTTPS 還是通過了 HTTP 來傳輸信息,但是信息通過 TLS 協議進行了加密。
TLS#
TLS 協議位於傳輸層之上,應用層之下。首次進行 TLS 協議傳輸需要兩個 RTT,接下來可以通過 Session Resumption 減少到一個 RTT。
在 TLS 中使用了兩種加密技術,分別為:對稱加密和非對稱加密。
對稱加密:
對稱加密就是兩邊擁有相同的秘鑰,兩邊都知道如何將密文加密解密。
非對稱加密:
有公鑰私鑰之分,公鑰所有人都可以知道,可以將數據用公鑰加密,但是將數據解密必須使用私鑰解密,私鑰只有分發公鑰的一方才知道。
TLS 握手過程如下圖:
- 客戶端發送一個隨機值,需要的協議和加密方式
- 服務端收到客戶端的隨機值,自己也產生一個隨機值,並根據客戶端需求的協議和加密方式來使用對應的方式,發送自己的證書(如果需要驗證客戶端證書需要說明)
- 客戶端收到服務端的證書並驗證是否有效,驗證通過會再生成一個隨機值,通過服務端證書的公鑰去加密這個隨機值並發送給服務端,如果服務端需要驗證客戶端證書的話會附帶證書
- 服務端收到加密過的隨機值並使用私鑰解密獲得第三個隨機值,這時候兩端都擁有了三個隨機值,可以通過這三個隨機值按照之前約定的加密方式生成密鑰,接下來的通信就可以通過該密鑰來加密解密
通過以上步驟可知,在 TLS 握手階段,兩端使用非對稱加密的方式來通信,但是因為非對稱加密損耗的性能比對稱加密大,所以在正式傳輸數據時,兩端使用對稱加密的方式通信。
PS:以上說明的都是 TLS 1.2 協議的握手情況,在 1.3 協議中,首次建立連接只需要一個 RTT,後面恢復連接不需要 RTT 了。
HTTP 2.0#
HTTP 2.0 相比於 HTTP 1.X,可以說是大幅度提高了 web 的性能。
在 HTTP 1.X 中,為了性能考慮,我們會引入雪碧圖、將小圖內聯、使用多個域名等等的方式。這一切都是因為瀏覽器限制了同一個域名下的請求數量,當頁面中需要請求很多資源的時候,隊頭阻塞(Head of line blocking)會導致在達到最大請求數量時,剩餘的資源需要等待其他資源請求完成後才能發起請求。
二進制傳輸
HTTP 2.0 中所有加強性能的核心點在於此。在之前的 HTTP 版本中,我們是通過文本的方式傳輸數據。在 HTTP 2.0 中引入了新的編碼機制,所有傳輸的數據都會被分割,並採用二進制格式編碼。
多路復用
在 HTTP 2.0 中,有兩個非常重要的概念,分別是幀(frame)和流(stream)。
幀代表著最小的數據單位,每個幀會標識出該幀屬於哪個流,流也就是多個幀組成的數據流。
多路復用,就是在一個 TCP 連接中可以存在多條流。換句話說,也就是可以發送多個請求,對端可以通過幀中的標識知道屬於哪個請求。通過這個技術,可以避免 HTTP 舊版本中的隊頭阻塞問題,極大的提高傳輸性能。
Header 壓縮
在 HTTP 1.X 中,我們使用文本的形式傳輸 header,在 header 攜帶 cookie 的情況下,可能每次都需要重複傳輸幾百到幾千的字節。
在 HTTP 2.0 中,使用了 HPACK 壓縮格式對傳輸的 header 進行編碼,減少了 header 的大小。並在兩端維護了索引表,用於記錄出現過的 header,後面在傳輸過程中就可以傳輸已經記錄過的 header 的鍵名,對端收到數據後就可以通過鍵名找到對應的值。
伺服器 Push
在 HTTP 2.0 中,伺服器可以在客戶端某個請求後,主動推送其他資源。
可以想像以下情況,某些資源客戶端是一定會請求的,這時就可以採取伺服器 push 的技術,提前給客戶端推送必要的資源,這樣就可以相對減少一點延遲時間。當然在瀏覽器兼容的情況下你也可以使用 prefetch。
DNS#
DNS 的作用就是通過域名查詢到具體的 IP。
因為 IP 存在數字和英文的組合(IPv6),很不利於人類記憶,所以就出現了域名。你可以把域名看成是某個 IP 的別名,DNS 就是去查詢這個別名的真正名稱是什麼。
在 TCP 握手之前就已經進行了 DNS 查詢,這個查詢是操作系統自己做的。當你在瀏覽器中想訪問 www.google.com 時,會進行一下操作:
- 操作系統會首先在本地緩存中查詢
- 沒有的話會去系統配置的 DNS 伺服器中查詢
- 如果這時候還沒得話,會直接去 DNS 根伺服器查詢,這一步查詢會找出負責 com 這個一級域名的伺服器
- 然後去該伺服器查詢 google 這個二級域名
- 接下來三級域名的查詢其實是我們配置的,你可以給 www 這個域名配置一個 IP,然後還可以給別的三級域名配置一個 IP
以上介紹的是 DNS 迭代查詢,還有種是遞歸查詢,區別就是前者是由客戶端去做請求,後者是由系統配置的 DNS 伺服器做請求,得到結果後將數據返回給客戶端。
PS:DNS 是基於 UDP 做的查詢。
從輸入 URL 到頁面加載完成的過程
- 首先做 DNS 查詢,如果這一步做了智能 DNS 解析的話,會提供訪問速度最快的 IP 地址回來
- 接下來是 TCP 握手,應用層會下發數據給傳輸層,這裡 TCP 協議會指明兩端的端口號,然後下發給網路層。網路層中的 IP 協議會確定 IP 地址,並且指示了數據傳輸中如何跳轉路由器。然後包會再被封裝到數據鏈路層的數據幀結構中,最後就是物理層面的傳輸了
- TCP 握手結束後會進行 TLS 握手,然後就開始正式的傳輸數據
- 數據在進入伺服器之前,可能還會先經過負責負載均衡的伺服器,它的作用就是將請求合理的分發到多台伺服器上,這時假設伺服器會響應一個 HTML 文件
- 首先瀏覽器會判斷狀態碼是什麼,如果是 200 那就繼續解析,如果 400 或 500 的話就會報錯,如果 300 的話會進行重定向,這裡會有個重定向計數器,避免過多次的重定向,超過次數也會報錯
- 瀏覽器開始解析文件,如果是 gzip 格式的話會先解壓一下,然後通過文件的編碼格式知道該如何去解碼文件
- 文件解碼成功後會正式開始渲染流程,先會根據 HTML 構建 DOM 樹,有 CSS 的話會去構建 CSSOM 樹。如果遇到 script 標籤的話,會判斷是否存在 async 或者 defer,前者會並行進行下載並執行 JS,後者會先下載文件,然後等待 HTML 解析完成後順序執行,如果以上都沒有,就會阻塞住渲染流程直到 JS 執行完畢。遇到文件下載的會去下載文件,這裡如果使用 HTTP 2.0 協議的話會極大的提高多圖的下載效率。
- 初始的 HTML 被完全加載和解析後會觸發 DOMContentLoaded 事件
- CSSOM 樹和 DOM 樹構建完成後會開始生成 Render 樹,這一步就是確定頁面元素的佈局、樣式等等諸多方面的東西
- 在生成 Render 樹的過程中,瀏覽器就開始調用 GPU 繪製,合成圖層,將內容顯示在螢幕上了