HTTP的TCP鏈接管理

Socket是大部分應用層協議的基礎,常見的Socket主要有兩種類型:TCP和UDP,HTTP協議默認使用的是TCP類型的Socket,80端口。 爲何呢?由於TCP鏈接可靠,用其它協議也是容許的[1],然而,也正是由於一開始使用的是TCP鏈接,才使得後面的優化方案有可能被實現。html

1. 最開始HTTP鏈接

一個服務器須要服務不少請求,這些請求若是鏈接之後不斷開,就會有大量處於等待狀態的鏈接須要維護,而進程、內存資源、帶寬資源、文件描述符數量有上限,針對客戶端與服務器端交換數據間歇性較大的特色,因此,最開始——HTTP1.0版本——將協議設計爲請求時建鏈接、請求完釋放鏈接,以儘快將資源釋放出來服務其餘客戶端。[2]前端

形成的結果就是:早期的HTTP服務器實現方式是每個HTTP的請求/響應過程都建立一次新的TCP鏈接,響應完就斷開,下一個請求過來再建立一次鏈接。 這在當時是合理的解決方案:簡單粗暴、不用管理鏈接、性能也比較好。node

但接下來,HTTP的發展迅猛,網頁中加入了愈來愈多的圖片、表單,單個用戶短期內須要屢次請求服務器上的資源,無鏈接已經不能知足日益增加的需求,哪怕犧牲點服務器資源來維護這個TCP鏈接,也比每次請求一個資源都創建一個新請求的開銷要小,這個開銷包括網絡帶寬、時間、建立、關閉鏈接……nginx

2. 一條TCP鏈接要完成屢次HTTP請求/響應過程

因此,你們試着在HTTP1.0的基礎上,擴展了一個新的Header:Keep-Alive: timeout=5, max=100,發現效果不錯。在HTTP1.1中,乾脆直接把默認狀態設置成開啓,除非顯式聲明Connection: Close的時候纔不保持開啓狀態。git

這也就是你們常常說的長鏈接(Persistent Connections)了,對於客戶端與服務器須要頻繁傳輸數據的場景,長鏈接比較合適。github

長鏈接開啓之後有不少用法,好比HTTP Pipelining。web

3. HTTP Pipelining技術

瀏覽器在解析完html之後,發現有三個圖片須要下載,這個時候怎麼用這一條隨時能夠雙向通訊的長鏈接來完成三個資源的獲取呢? 方法一:發起第一個圖片的請求,而後等待服務器返回結果,再發起第二個圖片的請求……直到都得到。 方法二:發起第一個圖片的請求,而後接着解析,發現須要第二個圖片,就接着發第二個圖片的請求(無論第一個請求有沒有返回),而後等待服務器返回結果,而服務器保證:三個響應按照我三個請求的順序返回就能夠了。 算法

優勢:完成時間明顯提早,這是RFC規範的一部分 缺點:可是你們都不遵照規範 [3] 你們費了九牛二虎之力達成了一致,制訂出了標準,最後你們又不用了!爲啥?

  1. 使用的是FIFO,容易致使Head-of-line blocking,就是:即使第二三四個請求都已經準備好了,若是第一個請求沒有完成,也要等到第一個請求準備好再發送,所以實際優化效果並不明顯
  2. 重要緣由是:服務器若是開啓,存在DDos風險——憋一堆拼湊起來的大請求一塊兒發出,服務器就扛不住了
  3. 只能是冪等的HTTP方法——多是由於須要重試,若是POST重試屢次,形成重複提交問題
  4. 還有代理服務器不穩定等等 但依然有人在爲此做出努力,並給出了一些解決方案但依然不流行

4. Pipelining以外的思路

一條TCP鏈接雖然能夠複用屢次了,但對於須要並行加載的環境,Pipelining因爲各類限制和缺陷也不堪重用,那索性,你們都不按規矩辦事,你規範既然說:chrome

Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. 只是個SHOULD NOT,你沒說MUST NOT呀,因此,我多建立幾條試試,你們通過多個版本的調整,你們以爲對於同一個域,這個值設置在4-6個併發鏈接還比較靠譜。express

固然,這個默認值用戶也是能夠修改的 再後來的事情,就是應用程序開發者利用這一特性作了多域名、CDN部署等優化,你們就都比較熟悉了。

5. 如何實時得到服務器上的最新消息?

5.1 輪詢(polling)

輪詢的問題:在沒有數據更新的時候,請求無心義,關鍵點在於輪詢時間的把握——須要綜合考慮用戶數量、實時行要求和服務器性能 毫無疑問,這是一個假的實時方案

5.2 comet

客戶端發起一個鏈接,啥時候有更新啥時候返回結果,客戶端等返回結果之後,立馬創建一個新的鏈接等待返回新數據。好聰明的方案!

5.3 HTTP的chunked技術 [4]

服務器準備好一部份內容,就輸出一部份內容,瀏覽器接收到一部份內容,就響應一部份內容 實現方法:

  1. 語言實現: Java、PHP: flush() Node原生: 只要沒執行end,write便可輸出,無需flush方法——若是真的沒有輸出的話,強制調用一下response.flushHeaders()應該也是能夠的 Express:通常來講,框架提供了更好的封裝而不具有相對低層次操做的藉口,好比express的render,一次性輸出了全部內容,因此貌似沒有方法能夠在express中分部分輸出——我還沒具體看源碼
  2. XHR的支持——Firefox和Safari的支持更好,直接就支持,Chrome須要設置一個header:X-Content-Type-Options: nosniff我是在這個問題中獲得解決方案的,另外,在curl中,若是內容結尾不加換行符號,輸出結果不會被curl立馬輸出。
  3. 注意:若是存在Nginx等負載均衡設備,須要在這些服務的配置中關掉緩存邏輯或者在Header中聲明不備緩存X-Accel-Buffering: no。若是還有問題,那有多是中間某個環節沒有開啓TCP的TCP_NODELAY參數。 Wireshare監聽到的分段請求:

分段的細節:

In the following example, three chunks of length 4, 5 and 14 are shown. The chunk size is transferred as a hexadecimal number followed by rn as a line separator, followed by a chunk of data of the given size.

4\r\n
  Wiki\r\n
  5\r\n
  pedia\r\n
  E\r\n
  in\r\n
  \r\n
  chunks.\r\n
  0\r\n
  \r\n
  解析結果:
  Wikipedia in
  chunks.
複製代碼

擴展:TCP默認開啓的Nagle算法

在單一TCP鏈路上,短期內須要傳輸多個短片斷的時候,是按照下面這張圖的模式來傳輸:

關閉了Nagle算法的TCP鏈接
能夠看到,每個小的TCP段都源源不斷地在網絡上傳輸,這就帶來了一個問題:接收端若是一直沒有收到TCP段,怎麼辦? 解決方案是:在接收到第一個TCP段的確認信號以前,發送端先暫存後續須要發送的包,等收到第一個TCP段的確認信號之後,發送緩存中的暫存中的片斷,這樣,就大大減少了網絡上傳輸大量無用包的風險。

開啓了Nagle算法的TCP鏈接
更多Nagle算法的介紹能夠查看 www.potaroo.net/ispcol/2004…裏面的Interactive TCP一節

更有意思的是Nagle算法和延遲ACK兩個優化條件相遇的時候,會形成明顯的延遲

思考:Bigpipe是咋實現的?

原理仍是chunked,在此之上封裝了一些pagelet等概念,詳細介紹看這裏 根本上說,這是一種對HTML文件進行分段發送的實際應用。 那對JSON是否是能夠用相似技術來發送呢?固然有:eBay也出了一個jsonpipe方案,主要解決了一個json在發送的時候,若是是一個標準的json,不能被直接parse的問題。 { "id": 12345, "title": "Bruce Wayne", "price": "$199.99" } \n\n { "id": 67890, "title": "Bane", "price": "$299.99" }

區分:chunked與206請求不同

若是在DevTools中查看請求,會發現mp3文件的狀態碼多數時候是206,這個與分段傳輸有什麼區別? 206的狀況是:客戶端和服務器要一部分數據,好比:就要一個mp3的前3MB 而後等用戶播放這個音樂了,在哪一段時間,我再加載那一部分的數據,用戶點到了75%的進度,我再從請求75%的位置處請求一小段。這樣,就作到了分端下載。適用場景是文件下載、音樂或視頻文件的在線播放等。 迅雷等多線程下載工具應用的主要就是這個特性——同時從一個文件的不一樣位置處請求,所有完成了,再拼成一個完整的文件。 chunked,你能夠簡單理解爲,chunked是直播流輸出,而206是點播啥看啥。

5.4 SSE

Server-Sent Events,從技術上,沒有啥新東西,從標準化和API的設計層面來看,算是個進步吧。

  1. 傳輸內容是文本類型(text/event-stream, charset=utf8)
  2. 瀏覽器單向接受服務器發送
  3. 能夠在服務器端和前端自定義事件(默認是message) 須要注意的是,這個不屬於傳輸層面的創新,僅僅是應用層面的新協定,因此,不在RFC規範文檔裏面,而是由W3C來制訂規範這裏有詳細描述

5.5 WebSocket簡介

基於HTTP,經過升級,完成雙向鏈接——使用的仍是以前創建的TCP通道,握手完成,直接基於TCP通訊。 tools.ietf.org/html/rfc645… 可以雙向傳輸二進制流。 可以作的事情要多不少了,在此之上能夠本身開發協議,也能夠移植其它協議到這個鏈路上面來。 最經常使用的庫就是socket.io

5.6 HTTP2.0

主要特性

  1. 新的二進制傳輸
  2. 消息在共享的連接上多路複用,隨機發送和隨機接收,如何判斷?用id來區分。
  3. Header壓縮:已經發送過的header在以後的消息中再也不發送
  4. 能夠取消某個request
  5. ServerPush
  6. 瀏覽器支持是HTTPS only

新變化

HTTP2.0的多路複用和ServerPush使得原來針對HTTP協議自己的一些性能優化原則,都已經不那麼重要了:

  1. 請求不須要合併了
  2. 多域名部署也不用了
  3. 圖片inline寫法不須要了 另外,HTTP2.0也不是全部場景都適合的,好比說:大文件下載的狀況下。

思考:有了HTTP2.0了是否不須要chunked 、SSE、WebSocket了?

先看定位: chunked是HTTP1.1協議自己的一部分,是一種應用層數據傳輸方式。 SSE是基於chunked技術之上的一種新約定。 WebSocket能夠理解爲一種新形態的Socket,能作的事情和傳統Socket的基礎功能差很少。 而HTTP2.0,是對HTTP1.1的複用方式的改善,是服務器和瀏覽器的事情,並無對前端提供任何新的JS接口來操做這條鏈路和感知ServerPush等。 另一個問題是,升級了HTTP2.0之後,原有WebSocket的操做(握手、通訊的那一套,都是基於HTTP1.1來作的)就不能再使用了。基於HTTP2.0標準的WebSocket還處於比較初期的階段。WebSocket over HTTP/2WebSocket2 over HTTP/2

參考資料


  1. (www.w3.org/Protocols/r…) ↩︎

  2. blog.csdn.net/bingjing123… ↩︎

  3. stackoverflow.com/questions/3… ↩︎

  4. en.wikipedia.org/wiki/Chunke… ↩︎

相關文章
相關標籤/搜索