什么是長(zhǎng)連接?
長(zhǎng)連接還是短連接
相比于短連接,長(zhǎng)連接更節(jié)省資源。如果每發(fā)送一條消息就要?jiǎng)?chuàng)建鏈路、發(fā)起握手認(rèn)證、關(guān)閉鏈路釋放資源,會(huì)損耗大量的系統(tǒng)資源。長(zhǎng)連接只在首次創(chuàng)建時(shí)或者鏈路斷連重連才創(chuàng)建鏈路,鏈路創(chuàng)建成果之后服務(wù)提供者和消費(fèi)者會(huì)通過(guò)業(yè)務(wù)消息和心跳維系鏈路,實(shí)現(xiàn)多消息復(fù)用同一個(gè)鏈路節(jié)省資源。
HTTP1.1規(guī)定了默認(rèn)保持長(zhǎng)連接(HTTP persistent connection ,也有翻譯為持久連接),數(shù)據(jù)傳輸完成了保持TCP連接不斷開(kāi)(不發(fā)RST包、不四次握手),等待在同域名下繼續(xù)用這個(gè)通道傳輸數(shù)據(jù);相反的就是短連接。
HTTP首部的Connection: Keep-alive是HTTP1.0瀏覽器和服務(wù)器的實(shí)驗(yàn)性擴(kuò)展,當(dāng)前的HTTP1.1 RFC2616文檔沒(méi)有對(duì)它做說(shuō)明,因?yàn)樗枰墓δ芤呀?jīng)默認(rèn)開(kāi)啟,無(wú)須帶著它,但是實(shí)踐中可以發(fā)現(xiàn),瀏覽器的報(bào)文請(qǐng)求都會(huì)帶上它。如果HTTP1.1版本的HTTP請(qǐng)求報(bào)文不希望使用長(zhǎng)連接,則要在HTTP請(qǐng)求報(bào)文首部加上Connection: close。《HTTP權(quán)威指南》提到,有部分古老的HTTP1.0 代理不理解Keep-alive,而導(dǎo)致長(zhǎng)連接失效:客戶端-->代理-->服務(wù)端,客戶端帶有Keep-alive,而代理不認(rèn)識(shí),于是將報(bào)文原封不動(dòng)轉(zhuǎn)給了服務(wù)端,服務(wù)端響應(yīng)了Keep-alive,也被代理轉(zhuǎn)發(fā)給了客戶端,于是保持了“客戶端-->代理”連接和“代理-->服務(wù)端”連接不關(guān)閉,但是,當(dāng)客戶端第發(fā)送第二次請(qǐng)求時(shí),代理會(huì)認(rèn)為當(dāng)前連接不會(huì)有請(qǐng)求了,于是忽略了它,長(zhǎng)連接失效。書(shū)上也介紹了解決方案:當(dāng)發(fā)現(xiàn)HTTP版本為1.0時(shí),就忽略Keep-alive,客戶端就知道當(dāng)前不該使用長(zhǎng)連接。其實(shí),在實(shí)際使用中不需要考慮這么多,很多時(shí)候代理是我們自己控制的,如Nginx代理,代理服務(wù)器有長(zhǎng)連接處理邏輯,服務(wù)端無(wú)需做patch處理,常見(jiàn)的是客戶端跟Nginx代理服務(wù)器使用HTTP1.1協(xié)議&長(zhǎng)連接,而Nginx代理服務(wù)器跟后端服務(wù)器使用HTTP1.0協(xié)議&短連接。
在實(shí)際使用中,HTTP頭部有了Keep-Alive這個(gè)值并不代表一定會(huì)使用長(zhǎng)連接,客戶端和服務(wù)器端都可以無(wú)視這個(gè)值,也就是不按標(biāo)準(zhǔn)來(lái),譬如我自己寫(xiě)的HTTP客戶端多線程去下載文件,就可以不遵循這個(gè)標(biāo)準(zhǔn),并發(fā)的或者連續(xù)的多次GET請(qǐng)求,都分開(kāi)在多個(gè)TCP通道中,每一條TCP通道,只有一次GET,GET完之后,立即有TCP關(guān)閉的四次握手,這樣寫(xiě)代碼更簡(jiǎn)單,這時(shí)候雖然HTTP頭有Connection: Keep-alive,但不能說(shuō)是長(zhǎng)連接。正常情況下客戶端瀏覽器、web服務(wù)端都有實(shí)現(xiàn)這個(gè)標(biāo)準(zhǔn),因?yàn)樗鼈兊奈募中∮侄啵3珠L(zhǎng)連接減少重新開(kāi)TCP連接的開(kāi)銷(xiāo)很有價(jià)值。
以前使用libcurl做的上傳/下載,就是短連接,抓包可以看到:1、每一條TCP通道只有一個(gè)POST;2、在數(shù)據(jù)傳輸完畢可以看到四次握手包。只要不調(diào)用curl_easy_cleanup,curl的handle就可能一直有效,可復(fù)用。這里說(shuō)可能,因?yàn)檫B接是雙方的,如果服務(wù)器那邊關(guān)掉了,那么我客戶端這邊保留著也不能實(shí)現(xiàn)長(zhǎng)連接。
如果是使用windows的WinHTTP庫(kù),則在POST/GET數(shù)據(jù)的時(shí)候,雖然我關(guān)閉了句柄,但這時(shí)候TCP連接并不會(huì)立即關(guān)閉,而是等一小會(huì)兒,這時(shí)候是WinHTTP庫(kù)底層支持了跟Keep-alive所需要的功能:即便沒(méi)有Keep-alive,WinHTTP庫(kù)也可能會(huì)加上這種TCP通道復(fù)用的功能,而其它的網(wǎng)絡(luò)庫(kù)像libcurl則不會(huì)這么做。
1、概述
提高網(wǎng)絡(luò)性能優(yōu)化,很重要的一點(diǎn)就是降低延遲和提升響應(yīng)速度。
通常我們?cè)跒g覽器中發(fā)起請(qǐng)求的時(shí)候header部分往往是這樣的

keep-alive 就是瀏覽器和服務(wù)端之間保持長(zhǎng)連接,這個(gè)連接是可以復(fù)用的。在HTTP1.1中是默認(rèn)開(kāi)啟的。
2、連接的復(fù)用為什么會(huì)提高性能呢?
通常我們?cè)诎l(fā)起http請(qǐng)求的時(shí)候首先要完成tcp的三次握手,然后傳輸數(shù)據(jù),最后再釋放連接。三次握手的過(guò)程可以參考這里 TCP三次握手詳解及釋放連接過(guò)程
一次響應(yīng)的過(guò)程

在高并發(fā)的請(qǐng)求連接情況下或者同個(gè)客戶端多次頻繁的請(qǐng)求操作,無(wú)限制的創(chuàng)建會(huì)導(dǎo)致性能低下。
如果使用keep-alive

在timeout空閑時(shí)間內(nèi),連接不會(huì)關(guān)閉,相同重復(fù)的request將復(fù)用原先的connection,減少握手的次數(shù),大幅提高效率。
并非keep-alive的timeout設(shè)置時(shí)間越長(zhǎng),就越能提升性能。長(zhǎng)久不關(guān)閉會(huì)造成過(guò)多的僵尸連接和泄露連接出現(xiàn)。
那么okttp在客戶端是如果類(lèi)似于客戶端做到的keep-alive的機(jī)制。
長(zhǎng)連接的過(guò)期時(shí)間

上圖中的Keep-Alive: timeout=20,表示這個(gè)TCP通道可以保持20秒。另外還可能有max=XXX,表示這個(gè)長(zhǎng)連接最多接收XXX次請(qǐng)求就斷開(kāi)。對(duì)于客戶端來(lái)說(shuō),如果服務(wù)器沒(méi)有告訴客戶端超時(shí)時(shí)間也沒(méi)關(guān)系,服務(wù)端可能主動(dòng)發(fā)起四次握手?jǐn)嚅_(kāi)TCP連接,客戶端能夠知道該TCP連接已經(jīng)無(wú)效;另外TCP還有心跳包來(lái)檢測(cè)當(dāng)前連接是否還活著,方法很多,避免浪費(fèi)資源。
長(zhǎng)連接的數(shù)據(jù)傳送完成識(shí)別
使用長(zhǎng)連接之后,客戶端、服務(wù)端怎么知道本次傳輸結(jié)束呢??jī)刹糠郑?是判斷傳輸數(shù)據(jù)是否達(dá)到了Content-Length指示的大小;2動(dòng)態(tài)生成的文件沒(méi)有Content-Length,它是分塊傳輸(chunked),這時(shí)候就要根據(jù)chunked編碼來(lái)判斷,chunked編碼的數(shù)據(jù)在最后有一個(gè)空chunked塊,表明本次傳輸數(shù)據(jù)結(jié)束。更細(xì)節(jié)的介紹可以看這篇文章。
并發(fā)連接數(shù)的數(shù)量限制
在web開(kāi)發(fā)中需要關(guān)注瀏覽器并發(fā)連接的數(shù)量,RFC文檔說(shuō),客戶端與服務(wù)器最多就連上兩通道,但服務(wù)器、個(gè)人客戶端要不要這么做就隨人意了,有些服務(wù)器就限制同時(shí)只能有1個(gè)TCP連接,導(dǎo)致客戶端的多線程下載(客戶端跟服務(wù)器連上多條TCP通道同時(shí)拉取數(shù)據(jù))發(fā)揮不了威力,有些服務(wù)器則沒(méi)有限制。瀏覽器客戶端就比較規(guī)矩,限制了同域名下能啟動(dòng)若干個(gè)并發(fā)的TCP連接去下載資源。并發(fā)數(shù)量的限制也跟長(zhǎng)連接有關(guān)聯(lián),打開(kāi)一個(gè)網(wǎng)頁(yè),很多個(gè)資源的下載可能就只被放到了少數(shù)的幾條TCP連接里,這就是TCP通道復(fù)用(長(zhǎng)連接)。如果并發(fā)連接數(shù)少,意味著網(wǎng)頁(yè)上所有資源下載完需要更長(zhǎng)的時(shí)間(用戶感覺(jué)頁(yè)面打開(kāi)卡了);并發(fā)數(shù)多了,服務(wù)器可能會(huì)產(chǎn)生更高的資源消耗峰值。瀏覽器只對(duì)同域名下的并發(fā)連接做了限制,也就意味著,web開(kāi)發(fā)者可以把資源放到不同域名下,同時(shí)也把這些資源放到不同的機(jī)器上,這樣就完美解決了。
容易混淆的概念——TCP的keep alive和HTTP的Keep-alive
TCP的keep alive是檢查當(dāng)前TCP連接是否活著;HTTP的Keep-alive是要讓一個(gè)TCP連接活久點(diǎn)。它們是不同層次的概念。
TCP keep alive的表現(xiàn):
當(dāng)一個(gè)連接“一段時(shí)間”沒(méi)有數(shù)據(jù)通訊時(shí),一方會(huì)發(fā)出一個(gè)心跳包(Keep Alive包),如果對(duì)方有回包則表明當(dāng)前連接有效,繼續(xù)監(jiān)控。
這個(gè)“一段時(shí)間”可以設(shè)置。具體做法google吧。
HTTP 流水線技術(shù)
使用了HTTP長(zhǎng)連接(HTTP persistent connection )之后的好處,包括可以使用HTTP 流水線技術(shù)(HTTP pipelining,也有翻譯為管道化連接),它是指,在一個(gè)TCP連接內(nèi),多個(gè)HTTP請(qǐng)求可以并行,下一個(gè)HTTP請(qǐng)求在上一個(gè)HTTP請(qǐng)求的應(yīng)答完成之前就發(fā)起。從wiki上了解到這個(gè)技術(shù)目前并沒(méi)有廣泛使用,使用這個(gè)技術(shù)必須要求客戶端和服務(wù)器端都能支持,目前有部分瀏覽器完全支持,而服務(wù)端的支持僅需要:按HTTP請(qǐng)求順序正確返回Response(也就是請(qǐng)求&響應(yīng)采用FIFO模式),wiki里也特地指出,只要服務(wù)器能夠正確處理使用HTTP pipelinning的客戶端請(qǐng)求,那么服務(wù)器就算是支持了HTTP pipelining。
由于要求服務(wù)端返回響應(yīng)數(shù)據(jù)的順序必須跟客戶端請(qǐng)求時(shí)的順序一致,這樣也就是要求FIFO,這容易導(dǎo)致Head-of-line blocking:第一個(gè)請(qǐng)求的響應(yīng)發(fā)送影響到了后邊的請(qǐng)求,因?yàn)檫@個(gè)原因?qū)е翲TTP流水線技術(shù)對(duì)性能的提升并不明顯(wiki提到,這個(gè)問(wèn)題會(huì)在HTTP2.0中解決)。另外,使用這個(gè)技術(shù)的還必須是冪等的HTTP方法,因?yàn)榭蛻舳藷o(wú)法得知當(dāng)前已經(jīng)處理到什么地步,重試后可能發(fā)生不可預(yù)測(cè)的結(jié)果。POST方法不是冪等的:同樣的報(bào)文,第一次POST跟第二次POST在服務(wù)端的表現(xiàn)可能會(huì)不一樣。
在HTTP長(zhǎng)連接的wiki中提到了HTTP1.1的流水線技術(shù)對(duì)RFC規(guī)定一個(gè)用戶最多兩個(gè)連接的指導(dǎo)意義:流水線技術(shù)實(shí)現(xiàn)好了,那么多連接并不能提升性能。我也覺(jué)得如此,并發(fā)已經(jīng)在單個(gè)連接中實(shí)現(xiàn)了,多連接就沒(méi)啥必要,除非瓶頸在于單個(gè)連接上的資源限制迫使不得不多開(kāi)連接搶資源。