HTTP協議採用「請求-應答」模式。html
一、當使用普通模式(HTTP multiple connection,也稱爲短鏈接),即非KeepAlive模式時,每一個請求/應答客戶和服務器都要新建一個鏈接,完成以後當即斷開鏈接(HTTP協議爲無鏈接的協議)。web
二、當使用Keep-Alive模式(HTTP persistent connection ,又稱持久鏈接、鏈接重用)時,Keep-Alive功能使客戶端到服務器端的鏈接持續有效,後續對服務器進行數據請求時仍然會保持TCP鏈接不斷開(不會再發RST包、不會再進行四次握手),等待在同域名下繼續用這個通道傳輸數據,避免了從新創建鏈接,這樣更高效,性能更高。編程
http 1.0中 Keep-Alive 功能默認是關閉的,須要在http頭加入"Connection: Keep-Alive",才能啓用Keep-Alive;http 1.1中默認啓用Keep-Alive,若是加入"Connection: close ",才關閉。目前大部分瀏覽器都是用http1.1協議,也就是說默認都會發起Keep-Alive的鏈接請求了,因此是否能完成一個完整的Keep-Alive鏈接就看服務器設置狀況。windows
HTTP首部的Connection: Keep-alive是HTTP1.0瀏覽器和服務器的實驗性擴展,當前的HTTP1.1 RFC2616文檔沒有對它作說明,由於它所須要的功能已經默認開啓,無須帶着它。可是實踐中能夠發現,瀏覽器的報文請求都會帶上它。後端
若是HTTP1.1版本的HTTP請求報文不但願使用長鏈接,則要在HTTP請求報文首部加上Connection: close。瀏覽器
《HTTP權威指南》提到,有部分古老的HTTP1.0 代理不理解Keep-alive,而致使長鏈接失效:客戶端–>代理–>服務端,客戶端帶有Keep-alive,而代理不認識,因而將報文原封不動轉給了服務端,服務端響應了Keep-alive,也被代理轉發給了客戶端,因而保持了「客戶端–>代理」鏈接和「代理–>服務端」鏈接不關閉。可是,當客戶端再發送第二次請求時,代理會認爲首次鏈接不會有請求了,因而忽略了它,長鏈接失效。書上也介紹瞭解決方案:當發現HTTP版本爲1.0時,就忽略Keep-alive,客戶端就知道當前不應使用長鏈接。其實,在實際使用中不須要考慮這麼多,不少時候代理是咱們本身控制的,如Nginx代理,代理服務器有長鏈接處理邏輯,服務端無需作patch處理,常見的是:客戶端跟Nginx代理服務器使用HTTP1.1協議&長鏈接,而Nginx代理服務器跟後端服務器使用HTTP1.0協議&短鏈接。服務器
在實際使用中,HTTP頭部有了Keep-Alive這個值並不表明必定會使用長鏈接,客戶端和服務器端均可以無視這個值,也就是不按標準來,譬如我本身寫的HTTP客戶端多線程去下載文件,就能夠不遵循這個標準,併發的或者連續的屢次GET請求,都分開在多個TCP通道中,每一條TCP通道,只有一次GET,GET完以後,當即有TCP關閉的四次握手,這樣寫代碼更簡單,這時候雖然HTTP頭有Connection: Keep-alive,但不能說是長鏈接。網絡
正常狀況下客戶端瀏覽器、web服務端都有實現這個標準,由於它們的文件又小又多,保持長鏈接減小從新開TCP鏈接的開銷頗有價值。多線程
之前使用網絡庫 libcurl 作的上傳/下載,就是短鏈接,抓包能夠看到:一、每一條TCP通道只有一個POST;二、在數據傳輸完畢能夠看到四次握手包。只要不調用curl_easy_cleanup,curl的handle就可能一直有效,可複用。這裏說可能,由於鏈接是雙方的,若是服務器那邊關掉了,那麼我客戶端這邊保留着也不能實現長鏈接。併發
若是是使用windows的WinHTTP庫,則在POST/GET數據的時候,雖然我關閉了句柄,但這時候TCP鏈接並不會當即關閉,而是等一小會兒,這時候是WinHTTP庫底層支持了跟Keep-alive所須要的功能:即使沒有Keep-alive,WinHTTP庫也可能會加上這種TCP通道複用的功能,而其它的網絡庫像libcurl則不會這麼作。之前觀察過WinHTTP庫不會及時斷開TCP鏈接。
客戶端的長鏈接不可能無限期的拿着,會有一個超時時間,服務器有時候會告訴客戶端超時時間,譬如:
上圖中的Keep-Alive: timeout=20,表示這個TCP通道能夠保持20秒。另外還可能有max=XXX,表示這個長鏈接最多接收XXX次請求後就斷開。對於客戶端來講,若是服務器沒有告訴客戶端超時時間也不要緊,服務端可能主動發起四次握手斷開TCP鏈接,客戶端可以知道該TCP鏈接已經無效;另外TCP還有心跳包來檢測當前鏈接是否還活着,方法不少,避免浪費資源。
使用長鏈接以後,客戶端、服務端怎麼知道本次傳輸結束呢?
兩部分:
一、判斷傳輸數據是否達到了Content-Length指示的大小;
二、若是動態生成的文件沒有Content-Length,它是分塊傳輸(chunked),這時候就要根據chunked編碼的數據在最後有一個空chunked塊,代表本次傳輸數據結束。更細節的介紹能夠看這篇文章。
在web開發中須要關注瀏覽器併發鏈接的數量,RFC文檔說,客戶端與服務器最多就連上兩通道,但服務器、我的客戶端要不要這麼作就隨人意了,有些服務器就限制同時只能有1個TCP鏈接,致使客戶端的多線程下載(客戶端跟服務器連上多條TCP通道同時拉取數據)發揮不了威力,有些服務器則沒有限制。瀏覽器客戶端就比較規矩,知乎這裏有分析,限制了同域名下能啓動若干個併發的TCP鏈接去下載資源。併發數量的限制也跟長鏈接有關聯,打開一個網頁,不少個資源的下載可能就只被放到了少數的幾條TCP鏈接裏,這就是TCP通道複用(長鏈接)。若是併發鏈接數少,意味着網頁上全部資源下載完須要更長的時間(用戶感受頁面打開卡了);併發數多了,服務器可能會產生更高的資源消耗峯值。瀏覽器只對同域名下的併發鏈接作了限制,也就意味着,web開發者能夠把資源放到不一樣域名下,同時也把這些資源放到不一樣的機器上,這樣就完美解決了。
TCP的 keep-alive 是檢查當前TCP鏈接是否活着;而HTTP的 Keep-alive 是要讓一個TCP鏈接活久點。它們是不一樣層次的概念。
TCP keep alive的表現:
當一個鏈接「一段時間」沒有數據通信時,一方會發出一個心跳包(Keep Alive包),若是對方有回包則代表當前鏈接有效,繼續監控。
這個「一段時間」能夠設置。
WinHttp庫的設置:
WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL
Sets the interval, in milliseconds, to send a keep-alive packet over the connection. The default interval is 30000 (30 seconds). The minimum interval is 15000 (15 seconds). Using WinHttpSetOption to set a value lower than 15000 will return with ERROR_INVALID_PARAMETER.
libcurl的設置:
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
CURLOPT_TCP_KEEPALIVE
Pass a long. If set to 1, TCP keepalive probes will be sent. The delay and frequency of these probes can be controlled by the CURLOPT_TCP_KEEPIDLE and CURLOPT_TCP_KEEPINTVL options, provided the operating system supports them. Set to 0 (default behavior) to disable keepalive probes (Added in 7.25.0).
CURLOPT_TCP_KEEPIDLE
Pass a long. Sets the delay, in seconds, that the operating system will wait while the connection is idle before sending keepalive probes. Not all operating systems support this option. (Added in 7.25.0)
CURLOPT_TCP_KEEPINTVL
Pass a long. Sets the interval, in seconds, that the operating system will wait between sending keepalive probes. Not all operating systems support this option. (Added in 7.25.0)
CURLOPT_TCP_KEEPIDLE是空閒多久發送一個心跳包,CURLOPT_TCP_KEEPINTVL是心跳包間隔多久發一個。
使用了HTTP長鏈接(HTTP persistent connection )以後的好處,包括可使用HTTP 流水線技術(HTTP pipelining,也有翻譯爲管道化鏈接),它是指,在一個TCP鏈接內,多個HTTP請求能夠並行,下一個HTTP請求在上一個HTTP請求的應答完成以前就發起。從wiki上了解到這個技術目前並無普遍使用,使用這個技術必需要求客戶端和服務器端都能支持,目前有部分瀏覽器徹底支持,而服務端的支持僅須要:按HTTP請求順序正確返回Response(也就是請求&響應採用FIFO模式),wiki裏也特意指出,只要服務器可以正確處理使用HTTP pipelinning的客戶端請求,那麼服務器就算是支持了HTTP pipelining。
因爲要求服務端返回響應數據的順序必須跟客戶端請求時的順序一致,這樣也就是要求FIFO,這容易致使Head-of-line blocking:第一個請求的響應發送影響到了後邊的請求,由於這個緣由致使HTTP流水線技術對性能的提高並不明顯(wiki提到,這個問題會在HTTP2.0中解決)。另外,使用這個技術的還必須是冪等的HTTP方法,由於客戶端沒法得知當前已經處理到什麼地步,重試後可能發生不可預測的結果。POST方法不是冪等的:一樣的報文,第一次POST跟第二次POST在服務端的表現可能會不同。
在HTTP長鏈接的wiki中提到了HTTP1.1的流水線技術對RFC規定一個用戶*多兩個鏈接的指導意義:流水線技術實現好了,那麼多鏈接並不能提高性能。我也以爲如此,併發已經在單個鏈接中實現了,多鏈接就沒啥必要,除非瓶頸在於單個鏈接上的資源限制迫使不得很少開鏈接搶資源。
目前瀏覽器並不過重視這個技術,畢竟性能提高有限。