標籤:cache res 舉例 factory 線程 知識庫 deque stat tcp三向交握
Android網路編程(八)源碼解析OkHttp後篇[複用串連池]
1.引子
在瞭解OkHttp的複用串連池之前,我們首先要瞭解幾個概念。
TCP三向交握
通常我們進行HTTP串連網路的時候我們會進行TCP的三向交握,然後傳輸資料,然後再釋放串連。
TCP三向交握的過程為:
第一次握手:建立串連。用戶端發送串連請求報文段,將SYN位置為1,Sequence Number為x;然後,用戶端進入SYN_SEND狀態,等待伺服器的確認;
第二次握手:伺服器收到用戶端的SYN報文段,需要對這個SYN報文段進行確認,設定Acknowledgment Number為x+1(Sequence Number+1);同時,自己自己還要發送SYN請求資訊,將SYN位置為1,Sequence Number為y;伺服器端將上述所有資訊放到一個報文段(即SYN+ACK報文段)中,一併發送給用戶端,此時伺服器進入SYN_RECV狀態;
第三向交握:用戶端收到伺服器的SYN+ACK報文段。然後將Acknowledgment Number設定為y+1,向伺服器發送ACK報文段,這個報文段發送完畢以後,用戶端和伺服器端都進入ESTABLISHED狀態,完成TCP三向交握。
TCP四次分手
當用戶端和伺服器通過三向交握建立了TCP串連以後,當資料傳送完畢,中斷連線就需要進行TCP四次分手:
第一次分手:主機1(可以使用戶端,也可以是伺服器端),設定Sequence Number和Acknowledgment
Number,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有資料要發送給主機2了;
第二次分手:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence
第三次分手:主機2向主機1發送FIN報文段,請求關閉串連,同時主機2進入LAST_ACK狀態;
第四次分手:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,然後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以後,就關閉串連;此時,主機1等待2MSL後依然沒有收到回複,則證明Server端已正常關閉,那好,主機1也可以關閉串連了。
來看下面的圖加強下理解:
keepalive connections
當然大量的串連每次串連關閉都要三向交握四次分手的很顯然會造成效能低下,因此http有一種叫做keepalive connections的機制,它可以在傳輸資料後仍然保持串連,當用戶端需要再次擷取資料時,直接使用剛剛空閑下來的串連而不需要再次握手。
Okhttp支援5個並發KeepAlive,預設鏈路生命為5分鐘(鏈路空閑後,保持存活的時間)。
2.串連池(ConnectionPool)分析
引用計數
在okhttp中,在高層代碼的調用中,使用了類似於引用計數的方式跟蹤Socket流的調用,這裡的計數對象是StreamAllocation,它被反覆執行aquire與release操作,這兩個函數其實是在改變RealConnection中的List<Reference<StreamAllocation>>
的大小。(StreamAllocation.Java)
RealConnection是socket物理串連的封裝,它裡面維護了List<Reference<StreamAllocation>>
的引用。List中StreamAllocation的數量也就是socket被引用的計數,如果計數為0的話,說明此串連沒有被使用就是閒置,需要通過下文的演算法實現回收;如果計數不為0,則表示上層代碼仍然引用,就不需要關閉串連。
主要變數
串連池的類位於okhttp3.ConnectionPool:
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); /** The maximum number of idle connections for each address. */ //閒置socket最大串連數 private final int maxIdleConnections; //socket的keepAlive時間 private final long keepAliveDurationNs; // 雙向隊列 private final Deque<RealConnection> connections = new ArrayDeque<>(); final RouteDatabase routeDatabase = new RouteDatabase(); boolean cleanupRunning;
主要的變數有必要說明一下:
- executor線程池,類似於CachedThreadPool,需要注意的是這種線程池的工作隊列採用了沒有容量的SynchronousQueue,不瞭解它的請查看Java並發編程(六)阻塞隊列這篇文章。
Deque<RealConnection>
,雙向隊列,雙端隊列同時具有隊列和棧性質,經常在緩衝中被使用,裡面維護了RealConnection也就是socket物理串連的封裝。
- RouteDatabase,它用來記錄串連失敗的Route的黑名單,當串連失敗的時候就會把失敗的線路加進去。
public ConnectionPool() { //預設閒置socket最大串連數為5個,socket的keepAlive時間為5秒 this(5, 5, TimeUnit.MINUTES); } public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.maxIdleConnections = maxIdleConnections; this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); // Put a floor on the keep alive duration, otherwise cleanup will spin loop. if (keepAliveDuration <= 0) { throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration); } }
通過建構函式可以看出ConnectionPool預設的閒置socket最大串連數為5個,socket的keepAlive時間為5秒。
快取作業
ConnectionPool提供對Deque<RealConnection>
進行操作的方法分別為put、get、connectionBecameIdle和evictAll幾個操作。分別對應放入串連、擷取串連、移除串連和移除所有串連操作,這裡我們舉例put和get操作。
put操作
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); }
在添加到Deque<RealConnection>
之前首先要清理閒置線程,這個後面會講到。
get操作
RealConnection get(Address address, StreamAllocation streamAllocation) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.allocations.size() < connection.allocationLimit && address.equals(connection.route().address) && !connection.noNewStreams) { streamAllocation.acquire(connection); return connection; } } return null; }
遍曆connections緩衝列表,當某個串連計數的次數小於限制的大小並且request的地址和緩衝列表中此串連的地址完全符合。則直接複用緩衝列表中的connection作為request的串連。
總結
可以看出串連池複用的核心就是用Deque<RealConnection>
來儲存串連,通過put、get、connectionBecameIdle和evictAll幾個操作來對Deque進行操作,另外通過判斷串連中的計數對象StreamAllocation來進行自動回收串連。
TCP串連與OKHTTP複用串連池