HTTP是一種無連線的事務協議,底層使用的還是TCP,連線池複用的就是TCP連線,目的就是在一個TCP連線上進行多次的HTTP請求從而提高效能。每次HTTP請求結束的時候,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

顧名思義,Conent-Length表示實體內容長度,客戶端(伺服器)可以根據這個值來判斷資料是否接收完成。當請求的資源是靜態的頁面或圖片,伺服器很容易知道內容的大小,但如果遇到動態的內容,或者檔案太大想多次傳送怎麼辦?

使用Transfer-Encoding

當需要一邊產生資料,一邊發給客戶端,伺服器就需要使用 Transfer-Encoding: chunked 這樣的方式來代替 Content-Length,Chunk編碼將資料分成一塊一塊的傳送。它由若干個Chunk串連而成,以一個標明長度為0 的chunk標示結束。每個Chunk分為頭部和正文兩部分,頭部內容指定正文的字元總數(十六進位制的數字 )和數量單位(一般不寫),正文部分就是指定長度的實際內容,兩部分之間用回車換行(CRLF) 隔開。在最後一個長度為0的Chunk中的內容是稱為footer的內容,是一些附加的Header資訊。

對於如何判斷訊息實體的長度,實際情況還要複雜的多,可以參考這篇文章:https://zhanjindong.com/2015/05/08/http-keep-alive-header

總結下HttpClient如何判斷連線是否保持:

  1. 檢查返回response報文頭的Transfer-Encoding欄位,若該欄位值存在且不為chunked,則連線不保持,直接關閉。
  2. 檢查返回的response報文頭的Content-Length欄位,若該欄位值為空或者格式不正確(多個長度,值不是整數),則連線不保持,直接關閉。
  3. 檢查返回的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兩個方法。

參考文章

.