最近在寫代碼的時候,用到了HttpClient連接池,發現對於高並發的請求,效率提升很大。雖然知道是因為建立了長連接,導致請求效率提升,但是對於內部的原理還是不太清楚。后來在網上看到了HTTP協議的發展史,里面提到了一個屬性Connection:keep-alive,引起了我極大的興趣,覺得兩者之間必然存在聯系,果真當我查閱了一些資料之后,發現了HttpClient連接池連接保持、超時和失效的機制。
1、保持實現原理
要想保持連接,首先客戶端需要告訴服務器希望保持長連接,這就是所謂的Keep-Alive模式(又稱持久連接,連接重用),HTTP1.0中默認是關閉的,需要在HTTP頭加入"Connection: Keep-Alive",才能啟用Keep-Alive;HTTP1.1中默認啟用Keep-Alive,加入"Connection: close ",才關閉。
但客戶端設置了Keep-Alive並不能保證連接就可以保持,這里情況比較復雜。要想在一個TCP上進行多次的HTTP會話,關鍵是如何判斷一次HTTP會話結束了?非Keep-Alive模式下可以使用EOF(-1)來判斷,但Keep-Alive時服務器不會自動斷開連接,有兩種最常見的方式。
使用Conent-Length(HTTP協議)
顧名思義,Conent-Length表示實體內容長度,客戶端(服務器)可以根據這個值來判斷數據是否接收完成。當請求的資源是靜態的頁面或圖片,服務器很容易知道內容的大小,但如果遇到動態的內容,或者文件太大想多次發送怎么辦?
使用Transfer-Encoding(HTTP協議)
當需要一邊產生數據,一邊發給客戶端,服務器就需要使用 Transfer-Encoding: chunked 這樣的方式來代替 Content-Length,Chunk編碼將數據分成一塊一塊的發送。它由若干個Chunk串連而成,以一個標明長度為0 的chunk標示結束。每個Chunk分為頭部和正文兩部分,頭部內容指定正文的字符總數(十六進制的數字 )和數量單位(一般不寫),正文部分就是指定長度的實際內容,兩部分之間用回車換行(CRLF) 隔開。在最后一個長度為0的Chunk中的內容是稱為footer的內容,是一些附加的Header信息。
總結下HttpClient如何判斷連接是否保持:
- 檢查返回response報文頭的Transfer-Encoding字段,若該字段值存在且不為chunked,則連接不保持,直接關閉。
- 檢查返回的response報文頭的Content-Length字段,若該字段值為空或者格式不正確(多個長度,值不是整數),則連接不保持,直接關閉。
- 檢查返回的response報文頭的Connection字段(若該字段不存在,則為Proxy-Connection字段)值:
- 如果這倆字段都不存在,則1.1版本默認為保持, 1.0版本默認為連接不保持,直接關閉。
- 如果字段存在,若字段值為close 則連接不保持,直接關閉;若字段值為keep-alive則連接標記為保持。
2、 保持連接時間
保持時間計時開始時間為連接交換至連接池的時間。 保持時長計算規則為:獲取response中 Keep-Alive字段中timeout值,若該存在,則保持時間為 timeout值*1000,單位毫秒。若不存在,則連接保持時間設置為-1,表示為無窮。
3、保持過程中如何保證連接沒有失效?
很難保證。傳統阻塞I/O模型,只有當I/O操作的時候,socket才能響應I/O事件。當TCP連接交給連接管理器后,它可能還處於“保持連接”的狀態,但是無法監聽socket狀態和響應I/O事件。如果這時服務器將連接關閉的話,客戶端是沒法知道這個狀態變化的,從而也無法采取適當的手段來關閉連接。
針對這種情況,HttpClient采取一個策略,通過一個后台的監控線程定時的去檢查連接池中連接是否還“新鮮”,如果過期了,或者空閑了一定時間則就將其從連接池里刪除掉。ClientConnectionManager提供了 closeExpiredConnections和closeIdleConnections兩個方法