轉載請以鏈接形式標明出處:
本文出自:103style的博客
base on 3.12.0
目錄
- 背景
- 簡介
StreamAllocation
的成員變量StreamAllocation
的構造函數StreamAllocation
的相關方法- 小結
背景
HTTP 的版本從最初的 1.0版本
,到後續的 1.1版本
,再到後續的 google 推出的SPDY,後來再推出 2.0版本
,HTTP協議越來越完善。okhttp也是根據2.0和1.1/1.0作爲區分,實現了兩種連接機制.
http2.0解決了老版本(1.1和1.0)
最重要兩個問題:
- 連接無法複用
- head of line blocking (HOL)問題
http2.0 使用 多路複用 的技術,多個 stream
可以共用一個 socket
連接。每個 tcp
連接都是通過一個 socket
來完成的,socket
對應一個 host
和 port
,如果有多個stream
(即多個 Request) 都是連接在一個 host
和 port
上,那麼它們就可以共同使用同一個 socket
,這樣做的好處就是 可以減少TCP的一個三次握手的時間。
在OKHttp
裏面,負責連接的是 RealConnection 。
簡介
官方註釋是
StreamAllocation
是用來協調Connections
、Streams
和Calls
這三個實體的。
HTTP通信 執行 網絡請求Call
需要在 連接Connection
上建立一個新的 流Stream
,我們將 StreamAllocation
稱之 流 的橋樑,它負責爲一次 請求 尋找 連接 並建立 流,從而完成遠程通信。
然後我們先來看看 StreamAllocation
這個類是在什麼地方初始化的,以及在哪些地方用到?
選中StreamAllocation
的構造方法,在 AndroidStudio
中按 Alt + F7
,我們發現是在 RetryAndFollowUpInterceptor
這個攔截器intercept(Chain chain)
中創建的.
然後往下層攔截器傳遞,直到 ConnectInterceptor
以及 CallServerInterceptor
才繼續用到。
public final class RetryAndFollowUpInterceptor implements Interceptor {
...
@Override public Response intercept(Chain chain) throws IOException {
...
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
...
}
public final class ConnectInterceptor implements Interceptor {
...
@Override public Response intercept(Chain chain) throws IOException {
...
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
public final class CallServerInterceptor implements Interceptor {
...
@Override public Response intercept(Chain chain) throws IOException {
...
streamAllocation.noNewStreams();
...
}
...
}
StreamAllocation 的成員變量
- 由構造方法中傳入的變量:
public final Address address;
地址private final ConnectionPool connectionPool;
連接池public final Call call;
請求對象public final EventListener eventListener;
事件回調private final Object callStackTrace;
日誌private final RouteSelector routeSelector;
路由選擇器
private RouteSelector.Selection routeSelection;
選中的路由集合private Route route;
路由private RealConnection connection;
HTTP連接private HttpCodec codec;
HTTP請求的編碼和響應的解碼
StreamAllocation 的構造函數
在 RetryAndFollowUpInterceptor 中傳入了 :
- OkHttpClient 配置的 連接池.
- 根據 Request 構建了
Address
. - 構建的 RealCall 對象.
- OkHttpClient 配置的
eventListener
. - RealCall 中
captureCallStackTrace()
中配置的callStackTrace
- 根據相關參數構建的
RouteSelector
.
public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
EventListener eventListener, Object callStackTrace) {
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
this.callStackTrace = callStackTrace;
}
StreamAllocation 的相關方法
我們在簡介中介紹 StreamAllocation
在哪裏創建的時候,發現攔截器中主要調用的就是 newStream(...)
和 noNewStreams()
這兩個方法。那我們先來看看這兩個方法吧。
newStream(...)
主要就是獲取一個 可用的連接 和 對應連接協議的編解碼實例,並賦值給變量connection
和codec
.public HttpCodec newStream(...) { ... try { //在連接池中找到一個可用的連接 沒有則創建一個 RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); //獲取對應協議的 編解碼類 Http1Codec or Http2Codec HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); synchronized (connectionPool) { codec = resultCodec; return resultCodec; } } catch (IOException e) { throw new RouteException(e); } }
noNewStreams()
主要就是設置當前的connection
不能再創建新的流。public void noNewStreams() { Socket socket; Connection releasedConnection; synchronized (connectionPool) { releasedConnection = connection; socket = deallocate(true, false, false); if (connection != null) releasedConnection = null; } closeQuietly(socket); if (releasedConnection != null) { eventListener.connectionReleased(call, releasedConnection); } }
繼續看下 newStream(...)
獲取可用連接的流程。
-
findHealthyConnection(...)
通過findConnection(...)
得到一個連接,如果是新創建的連接,則直接返回,否則檢查 連接 是否已經可以開始承載新的流,不行則繼續findConnection(...)
.private RealConnection findHealthyConnection(...) throws IOException { while (true) { RealConnection candidate = findConnection(...); //如果是一個新創建的連接 則直接返回 synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } //否則檢查 連接 是否已經可以開始承載新的流 if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; } }
-
findConnection(...)
1.0
第一個synchronized (connectionPool)
這一段首先嚐試使用當前的變量connection
. 如果當前的connection
爲null
, 則通過Internal.instance.get(connectionPool, address, this, null);
在 連接池 中獲取對應address
的連接 賦值給connection
,如果當前的connection
不爲null
,則直接返回.2.0
如果上一步沒有找到可用連接,則看是否有其他可用路由。3.0
第二個synchronized (connectionPool)
這一段,3.1
如果有其他路由則先去連接池查詢看是否有對應連接,3.2
沒有的話則創建一個新的 RealConnection,並賦值給變量connection
.3.3
在連接池中找到的話則直接返回。4.0
然後新創建的連接開始握手連接,然後放入連接池,然後返回。
private RealConnection findConnection(...) throws IOException { ... //1.0 嘗試使用當前的 connection,或者查找 連接池 synchronized (connectionPool) { ... releasedConnection = this.connection; if (this.connection != null) { result = this.connection; releasedConnection = null; } if (!reportedAcquired) { releasedConnection = null; } if (result == null) { Internal.instance.get(connectionPool, address, this, null); if (connection != null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; } } } ... if (result != null) { return result; } //2.0 看是否有其他的路由 boolean newRouteSelection = false; if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) { newRouteSelection = true; routeSelection = routeSelector.next(); } //3.0 synchronized (connectionPool) { if (newRouteSelection) { //3.1 有其他路由則先去連接池查詢看是否有對應連接 } if (!foundPooledConnection) { ... //3.2 沒有的話則創建一個新的RealConnection,並賦值給變量 connection result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } // 3.3 在連接池中找到的話則直接返回 if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; } //4.0 新創建的連接開始握手連接 result.connect(...); synchronized (connectionPool) { reportedAcquired = true; Internal.instance.put(connectionPool, result); ... } ... return result; }
流程圖大概如下:
-
acquire(...)
從連接池找到對應連接 賦值給StreamAllocation
public void acquire(RealConnection connection, boolean reportedAcquired) { assert (Thread.holdsLock(connectionPool)); if (this.connection != null) throw new IllegalStateException(); this.connection = connection; this.reportedAcquired = reportedAcquired; connection.allocations.add(new StreamAllocationReference(this, callStackTrace)); }
-
獲取成員變量的一些方法.
public HttpCodec codec() { synchronized (connectionPool) { return codec; } } public Route route() { return route; } public synchronized RealConnection connection() { return connection; }
-
release()
資源釋放public void release() { Socket socket; Connection releasedConnection; synchronized (connectionPool) { releasedConnection = connection; socket = deallocate(false, true, false); if (connection != null) releasedConnection = null; } closeQuietly(socket); if (releasedConnection != null) { Internal.instance.timeoutExit(call, null); eventListener.connectionReleased(call, releasedConnection); eventListener.callEnd(call); } }
小結
-
介紹了
HTTP2
和HTTP1
的區別,HTTP2
使用 多路複用 的技術,減少了同地址的TCP握手時間。 -
介紹了
StreamAllocation
的主要方法newStream(...)
,以及其中獲取可用Connection
的具體邏輯. -
newStream(...)
返回的HttpCodec
這個 編解碼的示例後面也會介紹。
參考文章
如果覺得不錯的話,請幫忙點個贊。
以上