OkHttp源碼之socket連接池

OkHttp源碼之socket連接池

在整個okhttp中,相對來說最耗資源的應該屬於socket連接了,所以為了節省tcp的連接釋放以及TLS協議的握手等時間,socket連接池是必不可少的。研究它的連接池,我們重點關注以下兩點:

  • socket複用有何標準
  • 一個socket何時會被關閉?

okhttp的連接池代碼在ConnectionPool中,首先看下大致的結構:

OkHttp源碼之socket連接池

可以看到,這裡使用一個Deque<realconnection>來保存的,至於RealConnection,可以理解成對socket的包裝。這裡大家要注意到,對於連接池來說查詢從來不是什麼耗時操作,所以這裡其實用List也是可以的,沒有什麼大的影響。/<realconnection>

get操作(連接池的複用)

OkHttp源碼之socket連接池

可以看到就只是遍歷了所有的連接,然後判斷某個連接是否可以複用,我們看下複用的判斷代碼,在RealConnection中:

OkHttp源碼之socket連接池

OkHttp源碼之socket連接池

對這整個方法,裡面對好幾條複用規則作了判斷,我們將重點分析。

規則一:流數量要符合要求

一個socket如果被複用,那麼在多個請求併發進行的情況下,必然出現多個線程同時往一個socket中寫入數據,那這樣做是否允許呢?這要分成兩種情況:

http1.x

在http 1.x協議下,所有的請求的都是順序的,即使使用了管道技術(可以同時按順序連續發送請求,但消息的返回還是按照請求發送的順序返回)也是如此,因此一個socket在任何時刻只能有一個流在寫入,這意味著正在寫入數據的socket無法被另一個請求複用

http2.0

http2.0協議使用了多路複用技術,允許同一個socket在同一個時候寫入多個流數據,每個流有id,會進行組裝,因此,這個時候正在寫入數據的socket是可以被複用的。

為了區分兩種情況,okhttp記錄了每個socket流使用情況,同時設定了每個socket能同時使用多少流,很明顯,http1.x同一時間只能有一個流,http2.0能有無數個:

OkHttp源碼之socket連接池

現在再看這句if判斷就能理解了,當然noNewStreams是在某些特殊情況下防止連接被複用時設置的,比如服務端要求關閉這個連接,那當然也不能被複用了。

小結

http1.x協議下當前socket沒有其他流正在讀寫時可以複用,否則不行,http2.0對流數量沒有限制。

規則二 http和ssl協議配置要相同

想要複用一個http連接,那麼兩次請求的所有http配置和ssl配置都要相同:

OkHttp源碼之socket連接池

具體的equalsNonHost方法實現如下:

OkHttp源碼之socket連接池

上面具體到每個配置大家可以自己研究,篇幅有限這裡不做展開。

規則三 域名要匹配

OkHttp源碼之socket連接池

這點不用說,滿足了以上三條規則後我們可以放心的複用這個連接了。

規則四 特殊情況

上面的三條規則如果都符合了自然是完美複用一個連接,但其實還有一種情況也是可以複用的:多個host指向同一個ip地址的情況。

在http1.x的情況,有些網站為了突破瀏覽器一個域名只能建立6-8個連接的限制,會給同一個ip地址配置不同的域名,這樣瀏覽器就能使用很多連接來訪問頁面,加快頁面打開速度,但在http2.0時代,有了多路複用,一個連接完全能滿足以前的要求,所以針對這種特殊情況我們應該複用連接,而不是新開連接,當然這個條件是比較苛刻的。

只有在http2.0情況下才會考慮複用

OkHttp源碼之socket連接池

只有在沒有代理時才能複用

OkHttp源碼之socket連接池

如果設置了代理,我們無法知道原始服務器的ip地址,自然無法判斷這個域名和之前的連接是否共用一個ip地址,自然不能複用。

只有ip地址相同才能複用

OkHttp源碼之socket連接池

這點是肯定的,ip地址不同socket連接肯定不可能複用,無需解釋。

對不受信任的證書處理方式相同才能複用

OkHttp源碼之socket連接池

第一行要求對證書的處理必須是默認的OkHostnameVerifier才行,要是自己隨便實現的一個該接口,我們無法保證它和之前的連接實現是否一致,自然無法複用。

第二行就是要求對於這個不同的host,必須通過之前的連接的證書校驗才行:

OkHttp源碼之socket連接池

這是因為複用socket連接其實就意味著跳過了https握手的過程,如果不通過證書校驗太危險了。、

本地證書校驗通過才能複用

https為了防止中間人攻擊可以在建立連接成功後將服務器下發的證書保存下來,這樣如果有中間人偽裝服務器,中間人下發的證書和本地保存的不一致就會校驗失敗,最後一條規則就是保證這個校驗要通過:

OkHttp源碼之socket連接池

小結

通過上面所有的校驗後,這種情況下,不同host同一ip地址的情況也是可以複用的。

連接池的清理

對於一個socket連接池來說必然有自己的清理機制,否則如果長期不發起網絡請求,socket連接一直被佔用就划不來了,okhttp是通過一個單獨的線程來清理的。

何時開始清理工作:

OkHttp源碼之socket連接池

每次put一個新連接的時候都會判斷是否需要清理,就是說並不會每次put都執行,要看條件控制的,然後我們看下具體清理邏輯:

OkHttp源碼之socket連接池

這裡似乎看不出什麼,我們繼續看cleanup()方法:

OkHttp源碼之socket連接池

OkHttp源碼之socket連接池

代碼看起來很長,其實原理很簡單,就是遍歷當前所有連接,跳過正在使用的連接,其他沒有用的連接,如果哪個連接超過了規定的時間,就關掉這個socket。如果都沒有超過規定時間的,就返回離規定時間最近的那個差值。

拿到那個時間值後,我們再回到上面那個cleanupRunnable中,在那裡會wait線程,然後醒來繼續清理。

舉例:socket最長生存時間是30分鐘,當前有5個連接,c1,c2正在被使用,c3空閒了40分鐘,c4空閒了20分鐘,c5空閒了25分鐘,

那麼一次clean()方法會關掉c3,然後返回0,在cleanupRunnable中會立馬進行下一次循環清理,這個時候檢測到離生存時間最近的是c5,那麼clean()方法會返回5分鐘這個時間值,cleanRunnable中會wait 5分鐘,然後5分鐘後會繼續下一次清理。

小結

理論上來說,只要連接池中有連接,該清理線程就一直存在,直到所有連接被釋放該線程才會停止。

-----點擊上方關注持續收聽面試乾貨

OkHttp源碼之socket連接池

私信 “666” 索要Android高級視頻(Flutter進階,插件化,熱修復技術)


分享到:


相關文章: