android之http协议编程(源码ppt),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> 的大小。(StreamAllocation.java)
public void acquire(RealConnection connection) {
connection.allocations.add(new WeakReference<>(this));
}
private void release(RealConnection connection) {
for (int i = 0, size = connection.allocations.size(); i < size; i++) {
Reference reference = connection.allocations.get(i);
if (reference.get() == this) {
connection.allocations.remove(i);
return;
}
}
throw new IllegalStateException();
}
RealConnection是socket物理連接的包裝,它里面維護了List>的引用。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(), 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 connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
主要的變量有必要說明一下:
executor線程池,類似于CachedThreadPool,需要注意的是這種線程池的工作隊列采用了沒有容量的SynchronousQueue,不了解它的請查看Java并發編程(六)阻塞隊列這篇文章。
Deque,雙向隊列,雙端隊列同時具有隊列和棧性質,經常在緩存中被使用,里面維護了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實例化是在OkHttpClient實例化時進行的:
public OkHttpClient() {
this(new Builder());
}
在OkHttpClient的構造函數中調用了new Builder():
public Builder() {
dispatcher = new Dispatcher();
...省略
connectionPool = new ConnectionPool();
...省略
}
緩存操作
ConnectionPool提供對Deque進行操作的方法分別為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之前首先要清理空閑的線程,這個后面會講到。
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的連接。
自動回收連接
okhttp是根據StreamAllocation引用計數是否為0來實現自動回收連接的。我們在put操作前首先要調用executor.execute(cleanupRunnable)來清理閑置的線程。我們來看看cleanupRunnable到底做了什么:
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
線程不斷的調用cleanup來進行清理,并返回下次需要清理的間隔時間,然后調用wait進行等待以釋放鎖與時間片,當等待時間到了后,再次進行清理,并返回下次要清理的間隔時間,如此循環下去,接下來看看cleanup方法:
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
//遍歷連接
for (Iterator i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//查詢此連接的StreamAllocation的引用數量,如果大于0則inUseConnectionCount數量加1,否則idleConnectionCount加1
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//如果空閑連接keepAlive時間超過5分鐘,或者空閑連接數超過5個,則從Deque中移除此連接
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
//如果空閑連接大于0,則返回此連接即將到期的時間
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
//如果沒有空閑連接,并且活躍連接大于0則返回5分鐘
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
//如果沒有任何連接則跳出循環
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
cleanup所做的簡單總結就是根據連接中的引用計數來計算空閑連接數和活躍連接數,然后標記出空閑的連接,如果空閑連接keepAlive時間超過5分鐘,或者空閑連接數超過5個,則從Deque中移除此連接。接下來根據空閑連接或者活躍連接來返回下次需要清理的時間數:如果空閑連接大于0則返回此連接即將到期的時間,如果都是活躍連接并且大于0則返回默認的keepAlive時間5分鐘,如果沒有任何連接則跳出循環并返回-1。在上述代碼中的第13行,通過pruneAndGetAllocationCount方法來判斷連接是否閑置的,如果pruneAndGetAllocationCount方法返回值大于0則是空閑連接,否則就是活躍連接,讓我們來看看pruneAndGetAllocationCount方法:
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List> references = connection.allocations;
//遍歷弱引用列表
for (int i = 0; i < references.size(); ) {
Reference reference = references.get(i);
//若StreamAllocation被使用則接著循環
if (reference.get() != null) {
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
Internal.logger.warning("A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?");
//若StreamAllocation未被使用則移除引用
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
//如果列表為空則說明此連接沒有被引用了,則返回0,表示此連接是空閑連接
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//否則返回非0的數,表示此連接是活躍連接
return references.size();
}
pruneAndGetAllocationCount方法首先遍歷傳進來的RealConnection的StreamAllocation列表,如果StreamAllocation被使用則接著遍歷下一個StreamAllocation,如果StreamAllocation未被使用則從列表中移除。如果列表為空則說明此連接沒有引用了,則返回0,表示此連接是空閑連接,否則就返回非0的數表示此連接是活躍連接。
總結
可以看出連接池復用的核心就是用Deque來存儲連接,通過put、get、connectionBecameIdle和evictAll幾個操作來對Deque進行操作,另外通過判斷連接中的計數對象StreamAllocation來進行自動回收連接。
總結
以上是生活随笔為你收集整理的android之http协议编程(源码ppt),Android网络编程(八)源码解析OkHttp中篇[复用连接池]...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android textview imp
- 下一篇: android 桌面图标创建,andro