序列連線
HTTP/0.9 和早期的 HTTP/1.0 協議對 HTTP 請求處理是序列化的。假如一個頁面包含 3 個樣式檔案,同屬於一個協議、域名、埠。那麼,瀏覽器一共需要發起四次請求,並且每次只能開啟一個 TCP 通道,在一個請求資源完成下載後,立刻斷開該連線,再開啟一個新的連線去處理佇列中的下一個請求。隨著頁面資源大小、數量的不斷擴增,網路延遲時間會不斷堆積,使用者會面對滿屏空白,等待過長時間而失去耐心。
並行連線
為了提高網路的吞吐能力,改進後的 HTTP 協議允許客戶端同時開啟多個 TCP 連線,並行地請求多個資源,充分利用頻寬。通常,每一個連線之間都會有一定延遲,但請求的傳輸時間是重疊的,總體上時延要比序列連線低很多。考慮到每一個連線都會消耗系統資源,並且伺服器需要處理海量的使用者併發請求,瀏覽器會對併發請求數量做一定的限制。即使 RFC 並沒有規定具體的限制數量,各瀏覽器廠商也都會有自己的標準:
- IE 7: 2
- IE 8/9: 6
- IE 10: 8
- IE 11: 13
- Firefox: 6
- Chrome: 6
- Safari: 6
- Opera: 6
- iOS WebView: 6
- Android WebView: 6
持久連線(長連線)
早期的 HTTP 協議對每個請求都佔用一個獨立的 TCP 連線,這無疑增加了 TCP 的建立連線開銷、擁塞控制開銷、釋放連線開銷,改進後的 HTTP/1.0 和 HTTP/1.1(預設)都支援了持久連線。如果一個請求完成後,不會立刻斷開連線,而是在一定的時間內保持連線,以便快速處理即將到來的 HTTP 請求,複用同一個 TCP 通道,直到客戶端心跳檢測失敗或伺服器連線超時。這個特性可以通過 HTTP 首部 Connection: keep-alive
來啟用,客戶端也可以傳送 Connection: close
來主動關閉連線。所以,我們看到,並行連線和持久連線這兩種優化是相輔相成的,並行連線使得首次載入頁面可以同時開啟多個 TCP 連線,而持久連線保證了後續的請求複用已開啟的 TCP 連線,這也是現代 Web 頁面的普遍機制。
管道化連線
持久連線讓我們可以重用連線來完成多次請求,但它必須滿足 FIFO 的佇列順序,必須保證前一個請求成功到達伺服器、處理成功並且收到伺服器返回的首個位元組,才可以發起佇列中下一個請求。HTTP 管道允許客戶端在同一個 TCP 通道內連續發起多個請求,而不必等待響應,消除了往返延遲時間差。但現實情況由於 HTTP/1.x 協議的限制,不允許資料在一個鏈路上交錯到達(IO 多路複用)。設想一種情況,客戶端伺服器端同時傳送一個 HTML 和多個 CSS 請求,伺服器並行處理所有請求,當所有的 CSS 請求處理完成並加入到緩衝佇列,卻發現 HTML 請求處理遇到問題而無限被掛起,嚴重時甚至造成緩衝區溢位,這種情況就叫做隊首阻塞。因此,這個方案在 HTTP/1.x 協議中並沒有被採納。
隊首阻塞並不是 HTTP 中獨有的概念,而是在快取式通訊網路交換中的一種普遍現象
總結
- 對於同一個協議、域名、埠,瀏覽器允許同時開啟個 TCP 連線,一般上限為 6 個。
- 同一個 TCP 連線允許發起多次 HTTP 請求,但必須等待前一個請求的首個位元組響應到達客戶端。
- 由於隊首阻塞問題,不允許客戶端同時傳送佇列中所有請求,這個問題在 HTTP/2.0 得已解決。