本文是從輸入URL到渲染頁面專欄的第二篇文章--網絡協議
咱們知道TCP/IP協議將網絡協議分了四層
html
咱們重點說下應用層,網絡層和傳輸層瀏覽器
數據在網絡上是如何傳輸的?
網絡層IP
數據想在互聯網上進行傳輸,就要符合網際協議(Internet Protocol,簡稱 IP)標準。互聯網上不一樣的在線設備都有惟一的地址標識,用一個數字來表示。
類比咱們日常網購,用咱們的收件地址類比設備的惟一標識,咱們知道了這個收件地址,就能夠往這個地址發送包裹。計算機的地址就稱爲 IP 地址,訪問任何網站實際上只是你的計算機向另一臺計算機請求信息。
若是要想把一個數據包從主機 A 發送給主機 B,那麼在傳輸以前,數據包上會被附加上主機 B 的 IP 地址信息,這樣在傳輸過程當中才能正確尋址。另外,數據包上還會附加上主機 A 的 IP 地址,有了這些信息主機 B 才能夠回覆信息給主機 A。這些附加的信息會被裝進一個叫 IP 頭的數據結構裏。緩存
下面咱們一塊兒來看下一個易於理解的數據包從主機 A 到主機 B 的簡化傳輸過程(不是4層網絡協議):服務器
- 上層將數據包交給網絡層;
- 網絡層再將 IP 頭附加到數據包上,組成新的 IP 數據包,並交給底層;
- 底層經過物理網絡將數據包傳輸給主機 B;
- 數據包被傳輸到主機 B 的網絡層,在這裏主機 B 拆開數據包的 IP 頭信息,並將拆開來的數據部分交給上層;
- 最終,含有信息的數據包就到達了主機 B 的上層了。
傳輸層UDP/TCP
上面咱們討論的基於IP傳輸是很是底層的協議,只負責把數據包傳送到對方電腦,可是對方電腦並不知道把數據包交給誰。所以,須要基於 IP 之上開發能和應用打交道的協議,也就是傳輸層,最多見的就是UDP和TCP協議。網絡
增長了傳輸層,咱們就能夠把前面的三層結構擴充爲四層結構,以下圖所示數據結構
下面咱們再一塊兒來看增長了傳輸層的數據傳輸路線:併發
- 上層將數據包交給傳輸層;
- 傳輸層會在數據包前面附加上 UDP/TCP 頭,組成新的數據包,再將新的數據包交給網絡層;
- 網絡層再將 IP 頭附加到數據包上,組成新的 IP 數據包,並交給底層;
- 數據包被傳輸到主機 B 的網絡層,在這裏主機 B 拆開 IP 頭信息,並將拆開來的數據部分交給傳輸層;
- 在傳輸層,數據包中的 UDP/TCP 頭會被拆開,並根據 UDP/TCP 中所提供的端口號,把數據部分交給上層的應用程序;
- 最終,數據包就到了主機 B 上層應用程序這裏。
那麼在傳輸層使用UDP和TCP傳輸有什麼區別呢,他們分別適合什麼場景呢,下面咱們來看一下性能
UDP和TCP的對比
在使用 UDP 發送數據時,有各類因素會致使數據包出錯,雖然 UDP 能夠校驗數據是否正確,可是對於錯誤的數據包,UDP 並不提供重發機制,只是丟棄當前的包,並且 UDP 在發送以後也沒法知道是否能達到目的地。雖然說 UDP 不能保證數據可靠性,可是傳輸速度卻很是快,因此 UDP 會應用在一些關注速度、但不那麼嚴格要求數據完整性的領域,如在線視頻、互動遊戲等。優化
UDP缺點:網站
- 數據包在傳輸過程當中容易丟失,且沒有重發機制;
- 大文件會被拆分紅不少小的數據包來傳輸,這些小的數據包會通過不一樣的路由,並在不一樣的時間到達接收端,而 UDP 協議並不知道如何組裝這些數據包,從而不能把這些數據包還原成完整的文件。
針對 UDP 的缺點,TCP頭除了包含了目標端口和本機端口號外,還提供了用於排序的序列號,以便接收端經過序號來重排數據包。
另外,傳輸能夠保證數據的可靠性,而且提供了重發機制。
那麼TCP是怎麼作到的呢,這就不得不提大名鼎鼎的「三次握手」和「四次揮手」了。
下面咱們看看一次完成的TCP傳輸過程是怎樣的:
- 首先,創建鏈接階段(三次握手)。TCP 提供面向鏈接的通訊傳輸。面向鏈接是指在數據通訊開始以前先作好兩端之間的準備工做。所謂三次握手,是指在創建一個 TCP 鏈接時,客戶端和服務器總共要發送三個數據包以確認鏈接的創建。
- 其次,傳輸數據階段。在該階段,接收端須要對每一個數據包進行確認操做,也就是接收端在接收到數據包以後,須要發送確認數據包給發送端。因此當發送端發送了一個數據包以後,在規定時間內沒有接收到接收端反饋的確認消息,則判斷爲數據包丟失,並觸發發送端的重發機制。一樣,一個大的文件在傳輸過程當中會被拆分紅不少小的數據包,這些數據包到達接收端後,接收端會按照 TCP 頭中的序號爲其排序,從而保證組成完整的數據。
- 最後,斷開鏈接階段(四次揮手)。數據傳輸完畢以後,就要終止鏈接了。
下面是網上很經典的三次握手和四次揮手的示意圖
- client發起鏈接,發送一個SYN包表示創建鏈接,還有一個SEQ = X(隨機數)
- server收到後,對client應答,併發送鏈接請求。發送一個既包含syn又包含ack的包,此時ack=X+1,SEQ = Y(隨機數)。
- client接到server的應答後,看到ack = X+1就知道server端已經接受了我以前的請求。對server的鏈接請求作應答,此時ack=Y+1
- 第一次client發出一個FIN包和一個seq=x,以後client進入FIN_WAIT_1階段
- server接收到以後回覆一個ack=x+1(原理同上),和seq=y,表示收到了client的關閉請求,以後進入CLOSE_WAIT狀態,client進入FIN_WAIT_2狀態
- 以後server處理完本身其餘的package以後發送一個ack=x+1,和seq=y,此時server進入LAST_ACK狀態,再也不回覆消息
- client接收到server的FIN包後回覆一個ack=y+1以後進入TIME_WAIT狀態,server接收到這個包以後直接進入CLOSED狀態,client等待了兩個msl(Maximum Segment Lifetime最大報文生存時間)以後沒有收到應答,表明server正常關閉,便也進入CLOSED狀態,關閉鏈接
到這裏你應該就明白了,TCP 爲了保證數據傳輸的可靠性,犧牲了數據包的傳輸速度
HTTP協議
接下來咱們看下應用層HTTP協議的發展歷史
HTTP1時代
HTTP/0.9
首先咱們來看看誕生最先的 HTTP/0.9。他的出現主要用於學術交流,需求很簡單——用來在網絡之間傳遞 HTML 超文本的內容,因此被稱爲超文本傳輸協議。總體來看,它的實現也很簡單,採用了基於請求響應的模式,從客戶端發出請求,服務器返回數據。
- 由於 HTTP 都是基於 TCP 協議的,因此客戶端先要根據 IP 地址、端口和服務器創建 TCP 鏈接,而創建鏈接的過程就是 TCP 協議三次握手的過程。
- 創建好鏈接以後,會發送一個 GET 請求行的信息,如GET /index.html用來獲取 index.html。
- 服務器接收請求信息以後,讀取對應的 HTML 文件,並將數據以 ASCII 字符流返回給客戶端。
- HTML 文檔傳輸完成後,斷開鏈接。
總的來講,當時的需求很簡單,就是用來傳輸體積很小的 HTML 文件,因此 HTTP/0.9 的實現有如下三個特色。
- 第一個是隻有一個請求行,並無 HTTP 請求頭和請求體,由於只須要一個請求行就能夠完整表達客戶端的需求了。
- 第二個是服務器也沒有返回頭信息,這是由於服務器端並不須要告訴客戶端太多信息,只須要返回數據就能夠了。
- 第三個是返回的文件內容是以 ASCII 字符流來傳輸的,由於都是 HTML 格式的文件,因此使用 ASCII 字節碼來傳輸是最合適的。
HTTP/1.0
隨着互聯網的發展,只能傳輸html已經不能知足需求了。還包括了 JavaScript、CSS、圖片、音頻、視頻等不一樣類型的文件。所以支持多種類型的文件下載是 HTTP/1.0 的一個核心訴求,並且文件格式不只僅侷限於 ASCII 編碼,還有不少其餘類型編碼的文件。
爲了讓客戶端和服務器能夠更靈活的交流,HTTP/1.0 引入了請求頭和響應頭,它們都是覺得 Key-Value 形式保存的,在 HTTP 發送請求時,會帶上請求頭信息,服務器返回數據時,會先返回響應頭信息。例以下面的代碼就是請求頭和響應頭信息的部分信息:
accept: text/html // 指望服務器返回 html 類型的文件
accept-encoding: gzip, deflate, br // 指望服務器能夠採用 gzip、deflate 或者 br 其中的一種壓縮方式
accept-Charset: ISO-8859-1,utf-8 // 表示指望返回的文件編碼是 UTF-8 或者 ISO-8859-1
accept-language: zh-CN,zh // 指望頁面的優先語言是中文
content-encoding: br // 表示服務器採用了 br 的壓縮方法
content-type: text/html; charset=UTF-8 // 表示服務器返回的是 html 文件,而且該文件的編碼類型是 UTF-8
這就是瀏覽器和服務器在1.0時代的一個交流方式,就好像兩我的在對「暗號」同樣。
HTTP/1.0除了對多文件提供良好的支持外,還引入了不少其餘的特性,這些特性都是經過請求頭和響應頭來實現的。
下面咱們來看看新增的幾個典型的特性:
- 有的請求服務器可能沒法處理,或者處理出錯,這時候就須要告訴瀏覽器服務器最終處理該請求的狀況,這就引入了狀態碼。狀態碼是經過響應行的方式來通知瀏覽器的。
- 爲了減輕服務器的壓力,提供了 Cache 機制,用來緩存已經下載過的數據。
- 服務器須要統計客戶端的基礎信息,好比 Windows 和 macOS 的用戶數量分別是多少,因此請求頭中還加入了用戶代理的字段。
HTTP/1.1
雖然1.0已經能夠應付絕大部分的場景,可是他仍是有如下幾個缺陷:
- 每進行一次 HTTP 通訊,都須要經歷創建 TCP 鏈接、傳輸 HTTP 數據和斷開 TCP 鏈接三個階段 —— 增長了持久鏈接的方法(Connection: keep-alive),它的特色是在一個 TCP 鏈接上能夠傳輸多個 HTTP 請求,只要瀏覽器或者服務器沒有明確斷開鏈接,那麼該 TCP 鏈接會一直保持。(目前瀏覽器中對於同一個域名,默認容許同時創建 6 個 TCP 持久鏈接)
- 隊頭阻塞問題 —— 沒有解決
- 每一個域名綁定了一個惟一的 IP 地址,所以一個服務器只能支持一個域名。可是隨着虛擬主機技術的發展,須要實如今一臺物理主機上綁定多個虛擬主機,每一個虛擬主機都有本身的單獨的域名,這些單獨的域名都公用同一個 IP 地址 —— 請求頭中增長了 Host 字段,用來表示當前的域名地址,這樣服務器就能夠根據不一樣的 Host 值作不一樣的處理。
- 須要在響應頭中設置完整的數據大小,如Content-Length: 901,這樣瀏覽器就能夠根據設置的數據大小來接收數據。不過隨着服務器端的技術發展,不少頁面的內容都是動態生成的,所以在傳輸數據以前並不知道最終的數據大小,這就致使了瀏覽器不知道什麼時候會接收完全部的文件數據 —— 經過引入 Chunk transfer 機制來解決這個問題,服務器會將數據分割成若干個任意大小的數據塊,每一個數據塊發送時會附上上個數據塊的長度,最後使用一個零長度的塊做爲發送數據完成的標誌。這樣就提供了對動態內容的支持。
HTTP2
雖然 HTTP/1.1 採起了不少優化資源加載速度的策略,也取得了必定的效果,可是 HTTP/1.1對帶寬的利用率卻並不理想,這也是 HTTP/1.1 的一個核心問題。(帶寬是指每秒最大能發送或者接收的字節數。咱們把每秒能發送的最大字節數稱爲上行帶寬,每秒可以接收的最大字節數稱爲下行帶寬。)之因此會出現這個問題,主要是由如下三個緣由致使的。
- TCP 的慢啓動。一旦一個 TCP 鏈接創建以後,就進入了發送數據狀態,剛開始 TCP 協議會採用一個很是慢的速度去發送數據,而後慢慢加快發送數據的速度,直到發送數據的速度達到一個理想狀態,咱們把這個過程稱爲慢啓動(相似汽車發動過程)。慢啓動是 TCP 爲了減小網絡擁塞的一種策略,咱們是沒有辦法改變的。而之因此說慢啓動會帶來性能問題,是由於頁面中經常使用的一些關鍵資源文件原本就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,一般這些文件在 TCP 鏈接創建好以後就要發起請求的,但這個過程是慢啓動,因此耗費的時間比正常的時間要多不少,這樣就推遲了寶貴的首次渲染頁面的時長了。
- 同時開啓了多個 TCP 鏈接,這些鏈接會競爭固定的帶寬。系統同時創建了多條 TCP 鏈接,當帶寬充足時,每條鏈接發送或者接收速度會慢慢向上增長;而一旦帶寬不足時,這些 TCP 鏈接又會減慢發送或者接收的速度。好比一個頁面有 200 個文件,使用了 3 個 CDN,那麼加載該網頁的時候就須要創建 6 * 3,也就是 18 個 TCP 鏈接來下載資源;在下載過程當中,當發現帶寬不足的時候,各個 TCP 鏈接就須要動態減慢接收數據的速度。這樣就會出現一個問題,由於有的 TCP 鏈接下載的是一些關鍵資源,如 CSS 文件、JavaScript 文件等,而有的 TCP 鏈接下載的是圖片、視頻等普通的資源文件,可是多條 TCP 鏈接之間又不能協商讓哪些關鍵資源優先下載,這樣就有可能影響那些關鍵資源的下載速度了。
- 隊頭阻塞。咱們知道在 HTTP/1.1 中使用持久鏈接時,雖然能公用一個 TCP 管道,可是在一個管道中同一時刻只能處理一個請求,在當前的請求沒有結束以前,其餘的請求只能處於阻塞狀態。
HTTP2推出了著名的多路複用技術來解決上面的三個問題。
- 一個域名只使用一個 TCP 長鏈接和消除隊頭阻塞問題。
- 將請求分紅一幀一幀的數據去傳輸,這樣請求能夠並行。
加入了多路複用技術後請求是如何進行的呢?
- 首先,瀏覽器準備好請求數據,包括了請求行、請求頭等信息,若是是 POST 方法,那麼還要有請求體。
- 這些數據通過二進制分幀層處理以後,會被轉換爲一個個帶有請求 ID 編號的幀,經過協議棧將這些幀發送給服務器。
- 服務器接收到全部幀以後,會將全部相同 ID 的幀合併爲一條完整的請求信息。
- 而後服務器處理該條請求,並將處理的響應行、響應頭和響應體分別發送至二進制分幀層。
- 一樣,二進制分幀層會將這些響應數據轉換爲一個個帶有請求 ID 編號的幀,通過協議棧發送給瀏覽器。
- 瀏覽器接收到響應幀以後,會根據 ID 編號將幀的數據提交給對應的請求。
經過上面的分析,咱們知道了多路複用是 HTTP/2 的最核心功能,它能實現資源的並行傳輸。多路複用技術是創建在二進制分幀層的基礎之上。基於二進制分幀層,HTTP/2 還實現了不少其餘功能,下面咱們就來簡要了解下。
- 能夠設置請求的優先級。咱們知道瀏覽器中有些數據是很是重要的,可是在發送請求時,重要的請求可能會晚於那些不怎麼重要的請求,若是服務器按照請求的順序來回複數據,那麼這個重要的數據就有可能推遲好久才能送達瀏覽器,這對於用戶體驗來講是很是不友好的。爲了解決這個問題,HTTP/2 提供了請求優先級,能夠在發送請求時,標上該請求的優先級,這樣服務器接收到請求以後,會優先處理優先級高的請求。
- 服務器推送。HTTP/2 還能夠直接將數據提早推送到瀏覽器。當用戶請求一個 HTML 頁面以後,服務器知道該 HTML 頁面會引用的JavaScript 文件和 CSS 文件,那麼在接收到 HTML 請求以後,附帶將要使用的 CSS 文件和 JavaScript 文件一併發送給瀏覽器,這樣當瀏覽器解析完 HTML 文件以後,就能直接拿到須要的 CSS 文件和 JavaScript 文件,這對首次打開頁面的速度起到了相當重要的做用。
- 頭部壓縮。HTTP/2 對請求頭和響應頭進行了壓縮。一方面,頭信息使用gzip或compress壓縮後再發送;另外一方面,客戶端和服務器同時維護一張頭信息表,全部字段都會存入這個表,生成一個索引號,之後就不發送一樣字段了,只發送索引號,這樣就提升速度了。