轉(zhuǎn)載請(qǐng)以鏈接形式標(biāo)明出處:
本文出自:103style的博客
base on 3.12.0
目錄
- 背景
- 簡(jiǎn)介
-
StreamAllocation
的成員變量 -
StreamAllocation
的構(gòu)造函數(shù) -
StreamAllocation
的相關(guān)方法 - 小結(jié)
背景
HTTP 的版本從最初的 1.0版本
桑寨,到后續(xù)的 1.1版本
,再到后續(xù)的 google 推出的SPDY铆遭,后來(lái)再推出 2.0版本
屉来,HTTP協(xié)議越來(lái)越完善走越。okhttp也是根據(jù)2.0和1.1/1.0作為區(qū)分浩淘,實(shí)現(xiàn)了兩種連接機(jī)制.
http2.0解決了老版本(1.1和1.0)
最重要兩個(gè)問(wèn)題:
- 連接無(wú)法復(fù)用
- head of line blocking (HOL)問(wèn)題
http2.0 使用 多路復(fù)用 的技術(shù)痘绎,多個(gè) stream
可以共用一個(gè) socket
連接。每個(gè) tcp
連接都是通過(guò)一個(gè) socket
來(lái)完成的蛉幸,socket
對(duì)應(yīng)一個(gè) host
和 port
,如果有多個(gè)stream
(即多個(gè) Request) 都是連接在一個(gè) host
和 port
上丛晦,那么它們就可以共同使用同一個(gè) socket
,這樣做的好處就是 可以減少TCP的一個(gè)三次握手的時(shí)間奕纫。
在OKHttp
里面,負(fù)責(zé)連接的是 RealConnection 烫沙。
簡(jiǎn)介
官方注釋是
StreamAllocation
是用來(lái)協(xié)調(diào)Connections
匹层、Streams
和Calls
這三個(gè)實(shí)體的。
HTTP通信 執(zhí)行 網(wǎng)絡(luò)請(qǐng)求Call
需要在 連接Connection
上建立一個(gè)新的 流Stream
锌蓄,我們將 StreamAllocation
稱之 流 的橋梁升筏,它負(fù)責(zé)為一次 請(qǐng)求 尋找 連接 并建立 流,從而完成遠(yuǎn)程通信瘸爽。
然后我們先來(lái)看看 StreamAllocation
這個(gè)類是在什么地方初始化的您访,以及在哪些地方用到?
選中StreamAllocation
的構(gòu)造方法剪决,在 AndroidStudio
中按 Alt + F7
灵汪,我們發(fā)現(xiàn)是在 RetryAndFollowUpInterceptor
這個(gè)攔截器intercept(Chain chain)
中創(chuàng)建的.
然后往下層攔截器傳遞,直到 ConnectInterceptor
以及 CallServerInterceptor
才繼續(xù)用到柑潦。
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 的成員變量
- 由構(gòu)造方法中傳入的變量:
-
public final Address address;
地址 -
private final ConnectionPool connectionPool;
連接池 -
public final Call call;
請(qǐng)求對(duì)象 -
public final EventListener eventListener;
事件回調(diào) -
private final Object callStackTrace;
日志 -
private final RouteSelector routeSelector;
路由選擇器
-
-
private RouteSelector.Selection routeSelection;
選中的路由集合 -
private Route route;
路由 -
private RealConnection connection;
HTTP連接 -
private HttpCodec codec;
HTTP請(qǐng)求的編碼和響應(yīng)的解碼
StreamAllocation 的構(gòu)造函數(shù)
在 RetryAndFollowUpInterceptor 中傳入了 :
- OkHttpClient 配置的 連接池.
- 根據(jù) Request 構(gòu)建了
Address
. - 構(gòu)建的 RealCall 對(duì)象.
-
OkHttpClient 配置的
eventListener
. -
RealCall 中
captureCallStackTrace()
中配置的callStackTrace
- 根據(jù)相關(guān)參數(shù)構(gòu)建的
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 的相關(guān)方法
我們?cè)诤?jiǎn)介中介紹 StreamAllocation
在哪里創(chuàng)建的時(shí)候享言,發(fā)現(xiàn)攔截器中主要調(diào)用的就是 newStream(...)
和 noNewStreams()
這兩個(gè)方法。那我們先來(lái)看看這兩個(gè)方法吧渗鬼。
-
newStream(...)
主要就是獲取一個(gè) 可用的連接 和 對(duì)應(yīng)連接協(xié)議的編解碼實(shí)例览露,并賦值給變量connection
和codec
.public HttpCodec newStream(...) { ... try { //在連接池中找到一個(gè)可用的連接 沒(méi)有則創(chuàng)建一個(gè) RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); //獲取對(duì)應(yīng)協(xié)議的 編解碼類 Http1Codec or Http2Codec HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); synchronized (connectionPool) { codec = resultCodec; return resultCodec; } } catch (IOException e) { throw new RouteException(e); } }
-
noNewStreams()
主要就是設(shè)置當(dāng)前的connection
不能再創(chuàng)建新的流。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); } }
繼續(xù)看下 newStream(...)
獲取可用連接的流程譬胎。
-
findHealthyConnection(...)
通過(guò)findConnection(...)
得到一個(gè)連接差牛,如果是新創(chuàng)建的連接,則直接返回银择,否則檢查 連接 是否已經(jīng)可以開(kāi)始承載新的流多糠,不行則繼續(xù)findConnection(...)
.private RealConnection findHealthyConnection(...) throws IOException { while (true) { RealConnection candidate = findConnection(...); //如果是一個(gè)新創(chuàng)建的連接 則直接返回 synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } //否則檢查 連接 是否已經(jīng)可以開(kāi)始承載新的流 if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; } }
-
findConnection(...)
-
1.0
第一個(gè)synchronized (connectionPool)
這一段首先嘗試使用當(dāng)前的變量connection
. 如果當(dāng)前的connection
為null
, 則通過(guò)Internal.instance.get(connectionPool, address, this, null);
在 連接池 中獲取對(duì)應(yīng)address
的連接 賦值給connection
,如果當(dāng)前的connection
不為null
浩考,則直接返回. -
2.0
如果上一步?jīng)]有找到可用連接夹孔,則看是否有其他可用路由。 -
3.0
第二個(gè)synchronized (connectionPool)
這一段,3.1
如果有其他路由則先去連接池查詢看是否有對(duì)應(yīng)連接搭伤,3.2
沒(méi)有的話則創(chuàng)建一個(gè)新的 RealConnection只怎,并賦值給變量connection
.3.3
在連接池中找到的話則直接返回。 -
4.0
然后新創(chuàng)建的連接開(kāi)始握手連接怜俐,然后放入連接池身堡,然后返回。
private RealConnection findConnection(...) throws IOException { ... //1.0 嘗試使用當(dāng)前的 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 有其他路由則先去連接池查詢看是否有對(duì)應(yīng)連接 } if (!foundPooledConnection) { ... //3.2 沒(méi)有的話則創(chuàng)建一個(gè)新的RealConnection贴谎,并賦值給變量 connection result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } // 3.3 在連接池中找到的話則直接返回 if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; } //4.0 新創(chuàng)建的連接開(kāi)始握手連接 result.connect(...); synchronized (connectionPool) { reportedAcquired = true; Internal.instance.put(connectionPool, result); ... } ... return result; }
流程圖大概如下:
okhttp之StreamAllocation.findHealthyConnection(...) -
-
acquire(...)
從連接池找到對(duì)應(yīng)連接 賦值給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); } }
小結(jié)
介紹了
HTTP2
和HTTP1
的區(qū)別,HTTP2
使用 多路復(fù)用 的技術(shù)季稳,減少了同地址的TCP握手時(shí)間擅这。介紹了
StreamAllocation
的主要方法newStream(...)
,以及其中獲取可用Connection
的具體邏輯.newStream(...)
返回的HttpCodec
這個(gè) 編解碼的示例后面也會(huì)介紹景鼠。
參考文章
如果覺(jué)得不錯(cuò)的話仲翎,請(qǐng)幫忙點(diǎn)個(gè)贊唄。
以上
掃描下面的二維碼铛漓,關(guān)注我的公眾號(hào) Android1024溯香, 點(diǎn)關(guān)注,不迷路浓恶。