一垄懂、前言
【1.1】OkHttp系列其他篇章:
- 同步請求的實現(xiàn)流程瓶颠。
- 異步請求的實現(xiàn)流程
- 重要攔截器:CacheInterceptor 的解析拟赊。
- 重要攔截器:ConnectInterceptor 的解析。
- 重要攔截器:CallServerInterceptor 的解析粹淋。
【1.2】陳述
終于來到OkHttp的網(wǎng)絡(luò)連接模塊吸祟,這塊內(nèi)容是OkHttp的核心內(nèi)容。我們知道Http的連接需要進(jìn)行3此握手桃移,斷開需要4次揮手屋匕。而連接的每一次握手,都需要進(jìn)行Socket連接借杰、釋放过吻,這是一個非常麻煩而且耗時耗力的過程。那么連接的服用就顯得尤為重要了蔗衡,同個地址的連接纤虽,如果在用完后不斷開,保持連接粘都,在下次的請求中便能重復(fù)使用這個連接廓推,節(jié)省了連接的時間。這對于大部分時間需要重復(fù)頻繁訪問同一個服務(wù)器地址的移動端網(wǎng)絡(luò)來說更加不可或缺翩隧。
在本篇文章中樊展,我們將以ConnectIntercepter為起點呻纹,跟隨網(wǎng)絡(luò)連接獲取的過程,深入探究其中涉及到的:連接查找专缠、連接復(fù)用雷酪,網(wǎng)絡(luò)連接的建立(三次握手、Http2協(xié)議等的處理)涝婉。面對這復(fù)雜的過程哥力,我們先總體的走一遍連接獲取過程,然后在后續(xù)介紹 RealConnection.java 和 ConnectionPool.java 來更深入的理解連接的建立和緩存查找等邏輯墩弯。除此之外吩跋,我們還需要先看一下另一個類:Transmitter.java ,它將在connect的過程中起到重要的地位渔工。
二锌钮、Transmmiter:應(yīng)用和Http的橋梁
【2.1】來歷和作用
RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
Transmitter.java
public final class Transmitter {
private final OkHttpClient client;
//重點:連接池
private final RealConnectionPool connectionPool;
//此次請求
private final Call call;
private Request request;
//重點:連接查找器,它將承當(dāng)主要的連接查找工作引矩。
private ExchangeFinder exchangeFinder;
//Connecttion的實現(xiàn)類梁丘,代表著和服務(wù)器的連接。
public RealConnection connection;
//重點:負(fù)責(zé)請求的發(fā)送和響應(yīng)接收
private @Nullable Exchange exchange;
//請求是否已取消
private boolean canceled;
...
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
總結(jié):Transmitter是在創(chuàng)建RealCall的時候被創(chuàng)建的旺韭,其中需要了OkHttpClient和當(dāng)前請求Call作為參數(shù)氛谜。所以我們知道了,一個請求對應(yīng)著一個Transmitter区端。而且值漫,它的成員變量里有ExchangeFinder等類,負(fù)責(zé)為這個請求查找到一個合適的請求织盼。
【2.2】 releaseConnectionNoEvents()
這個方法是釋放一個連接惭嚣,該方法在后面的查找連接中會涉及到,我們在這里先對其進(jìn)行講述悔政。
Transmitter.java
@Nullable Socket releaseConnectionNoEvents() {
...
int index = -1;
//一個連接,可以有多個transmitter延旧,也就是用于多個請求谋国。所以在這里需要
//找到自己的那一個。
for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
Reference<Transmitter> reference = this.connection.transmitters.get(i);
if (reference.get() == this) {
index = i;
break;
}
}
if (index == -1) throw new IllegalStateException();
//將自己從連接中剔除掉迁沫。
RealConnection released = this.connection;
released.transmitters.remove(index);
this.connection = null;
//如果這個請求釋放了這個連接后芦瘾,這個連接沒有被用于其他請求
//調(diào)用連接池,使這個連接變?yōu)橐粋€空閑連接集畅。
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
//詳見【5.4】
if (connectionPool.connectionBecameIdle(released)) {
//沒人在用了近弟,把Socket返回回去。
return released.socket();
}
}
//還有其他請求在用挺智,就不返回socket回去祷愉。
return null;
}
總結(jié):這是一個請求關(guān)閉一個連接的過程。
- 先找到連接中對直接的索引,斷開二鳄。
- 判斷連接是否還有請求在用赴涵,不用使其變?yōu)榭臻e連接。
【2.1】prepareToConnect():連接的準(zhǔn)備工作
【2.2.1】
RetryAndFollowUpInterceptor.java
@Override public Response intercept(Chain chain) throws IOException {
...
Transmitter transmitter = realChain.transmitter();
...
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
...
}
}
從上面可以看到订讼,在執(zhí)行第一個默認(rèn)攔截器的邏輯的時候髓窜,調(diào)用transmitter.prepareToConnect()方法。我們接下去看一下這個方法做了上面準(zhǔn)備工作欺殿。
【2.2.2】prepareToConnect()
Transmitter.java
public void prepareToConnect(Request request) {
if (this.request != null) {
//如果這個Transmitter已經(jīng)有了一個請求了
//并且他們的url所指向的地址都是同一個寄纵,那么這個連接可以復(fù)用,直接返回脖苏。
if (sameConnection(this.request.url(), request.url())) return;
//如果上個請求的信息交換器不為空程拭,代表這次request還沒有結(jié)束
//那么拋出錯誤,該Transmitter不能給新的request用帆阳。
if (exchange != null) throw new IllegalStateException();
//釋放上次的連接哺壶。
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
//第一次進(jìn)來時,直接來到這里蜒谤。
this.request = request;
//給自己創(chuàng)建一個連接查找器山宾,注意這里的CreateAddress(),它將返回一個Adrees對象鳍徽,代表著遠(yuǎn)方服務(wù)器的一個地址资锰。
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
總結(jié):其實這個方法,重點就是為連接作準(zhǔn)備阶祭。但是主要目的還是找到可以復(fù)用的連接绷杜。它的邏輯如下:
- 如果這個Transmiiter之前已經(jīng)有過請求,而且和新請求所指向的地址是一樣的濒募,那么這個連接可以復(fù)用鞭盟,直接返回。
- 如果不是同個地址瑰剃,而且上個請求還沒用完(exchange != null)齿诉,那么這個Tranmitter不能復(fù)用。直接報錯晌姚。如果上個請求已經(jīng)完了粤剧, 釋放上個請求的連接。
- 如果這個Tranmitter是新的挥唠,那么給這個Transmiiter新創(chuàng)建一個ExcahgeFinder抵恋,請注意這個類,它很重要宝磨,將負(fù)責(zé)最主要的連接查找工作弧关。
【2.2】acquireConnectionNoEvents
Transmitter.java
void acquireConnectionNoEvents(RealConnection connection) {
...
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
總結(jié): 這個方法是代表Transmitter獲得了一個可用的連接了盅安。那么它做的工作是將這個連接保存起來。然后將自己登記到RealConnection梯醒。這個方法后面會有用到宽堆,這里先講解一下。
三茸习、查找連接
有了章節(jié)二的預(yù)備知識后畜隶,我們可以來看ConnectIntercepter了。不過他只是觸發(fā)打開連接的按鈕号胚,真正連接的查找和連接邏輯在exchangeFinder.java和Exchage.java籽慢。不管怎么樣,我們先來看一下開始的地方猫胁。
【3.1】ConnectIntercepter
ConnectIntercepter.java
@Override public Response intercept(Chain chain) throws IOException {
...
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//詳見【3.2】
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
調(diào)用transmitter的newExcahge()方法箱亿,得到一個可以與遠(yuǎn)程地址進(jìn)行通行的Exchage,然后就丟給下一個攔截器了弃秆。順帶說一下届惋,在第一篇《》我們知道,緊跟著ConnectIntercepter的下一個攔截器是ServerIntercepter菠赚,那我們可以很容易的推理出脑豹,它拿到了ConnectIntercepter的excahge后,就進(jìn)行了數(shù)據(jù)傳輸和數(shù)據(jù)接收衡查。
【3.2】newExchange()
Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
...
//詳見3.3:find()
//詳見四:ExchangeCodec.java
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
調(diào)用exchangeFinder.find()找到一個連接瘩欺,返回ExchangeCodec。ExchangeCodec是一個接口拌牲,它代表著Http請求的加密俱饿,和響應(yīng)的解密。它有2個具體實現(xiàn):Http1ExchangeCodec和Http2ExchangeCodec塌忽,它的詳細(xì)內(nèi)容詳見【4】拍埠。我們繼續(xù)看連接的查找。
【3.4】find()
ExcahgeFinder.java
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//詳見【3.5】
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//詳見【3.7】
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
【3.5】findHealthyConnection()
ExcahgeFinder.java
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
//詳見:【3.6】找到連接候選人
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// 如果這個連接是全新的土居,那么可以直接用
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 在這里需要檢查一下這個連接是否健康的
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
//如果不加看械拍,調(diào)用RealConnection.noNewExcahge()方法,將此連接丟棄并繼續(xù)找装盯。
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
總結(jié): 該方法顧名思義,就是通過一個while(true)不斷的找一個連接候選人甲馋,然后檢查是否健康可用的埂奈,如果不能用就進(jìn)行標(biāo)記,丟棄定躏。詳細(xì)的如下:
- 調(diào)用findConnection()找到一個連接账磺。
- 如果全新的直接用芹敌。
- 如果不健康的調(diào)用RealConnection.noNewExcahge(),它內(nèi)部主要做的是noNewExchanges = true; 這個標(biāo)志為后續(xù)將會用到垮抗,用來丟棄連接氏捞。
- 不見看的判斷:isHealthy() 就不展開了,就是判斷connection的socket等是否被關(guān)閉了冒版,進(jìn)而判斷連接是否健康液茎。
【3.6】findConnection()
接下來就是重中之重了,讓我們來一起品味這很香的查找邏輯辞嗡。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
//1.如果這個請求已經(jīng)被取消過捆等,那么再次請求拋出錯誤。
if (transmitter.isCanceled()) throw new IOException("Canceled");
...
//2. 先找到這個連接之前的路由結(jié)果
Route previousRoute = retryCurrentRoute()
? transmitter.connection.route()
: null;
//3. 在這里嘗試使用一個已經(jīng)分配好的連接续室,但是如上文【3.5】看到的
//他會檢查它的noNewExchange標(biāo)志為栋烤,如果是true 的話,那么這個連接不但不能用挺狰,而且還要復(fù)制給toClose明郭,關(guān)閉掉。
//詳見【2.3】:releaseConnectionNoEvents()
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
//4.如果transmitter的connection歷經(jīng)了上面的的邏輯丰泊,沒有被置空薯定,說明這個連接可用,賦值給result趁耗。
if (transmitter.connection != null) {
result = transmitter.connection;
releasedConnection = null; //這個連接可以用沉唠,不能把他釋放掉,重新置為空苛败。
}
//5. 如果此時的result還是為空满葛,說明上面嘗試獲取一個已經(jīng)分配好的連接失敗
//那么這次嘗試重連接池中獲取。
if (result == null) {
//詳見【5.3】:嘗試獲取一個連接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
//獲取成功
foundPooledConnection = true;
result = transmitter.connection;
} else {
//連接池都獲取失敗的話罢屈,需要進(jìn)行路由
selectedRoute = previousRoute;
}
}
}
//將剛剛要關(guān)閉的連接關(guān)閉嘀韧。
closeQuietly(toClose);
...
//result不空,找到一個可用的連接缠捌,直接返回锄贷。
if (result != null) {
return result;
}
// 6.進(jìn)行路由選擇
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
//7. 由于有新的路由,用路由選擇的新的IP集合曼月,再次此時到連接池中找可以復(fù)用的連接谊却。
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
//8. 既然路由都沒有找到可以用的,那么就創(chuàng)建一個新的RealConnection哑芹,
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 9. 如果剛剛第二次在連接池找到了炎辨,那么返回這個連接。
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 10. 詳見【4.2】說明要用新連接聪姿,那么進(jìn)行TCP+TSL連接
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
//11. 當(dāng)多個連接連接到同一個主機(jī)時碴萧,在這里會進(jìn)行連接合并乙嘀。這是最后一次嘗試
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
//說明連接池中已經(jīng)有一個可用的連接了,不需要剛剛創(chuàng)建的連接破喻。
pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
//12.詳見【5.5】新創(chuàng)建的連接正常使用虎谢,將它放入池子中
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
//如果有需要,丟掉剛剛新創(chuàng)建的連接
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
//終于可以返回
return result;
}
總結(jié):這是一個查找連接的過程曹质,在查找的時候婴噩,綜合考慮了自身的連接,路由的結(jié)果咆繁,連接池的復(fù)用讳推,和新建幾種方案。具體的如下:
- 嘗試獲取這個請求被已經(jīng)被分配的連接玩般,如果存在银觅,且可用,那么直接使用這個坏为,返回究驴。否則,這個連接的socket將會被關(guān)閉匀伏。
- 嘗試從連接池中獲取洒忧,如果可用,返回結(jié)果够颠。
- 嘗試使用路由器進(jìn)行路由選擇出路由結(jié)果集合熙侍,再次到連接池中進(jìn)行查找可用連接。
- 如果路由都找不到履磨,新建一個連接蛉抓,進(jìn)行TCP+TSL 連接。
- 再次到連接池中剃诅,看有沒有可用的連接巷送,避免多次連接造成重復(fù)創(chuàng)建。如果找到矛辕,關(guān)閉新建的連接笑跛。結(jié)果替換為新找到的連接。如果沒有聊品,將新連接放入連接池中飞蹂。
- 返回結(jié)果。
【3.7】 newCodec(): 獲得數(shù)據(jù)加解密器
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
if (http2Connection != null) {
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}
總結(jié): 根據(jù)連接性質(zhì)不一樣翻屈,生成不同的數(shù)據(jù)加解密器陈哑。
章節(jié)小結(jié):本節(jié)從ConnectIntercepter開始,追尋了一個連接如何被獲得的過程,它涉及到了新建連接芥颈、路由選擇,連接池復(fù)用等邏輯赚抡,最終的產(chǎn)物是Exchange爬坑,由它去到下一個攔截器:ServerIntercepter進(jìn)行網(wǎng)絡(luò)傳輸工作。其中Exchange涂臣、RealConnectionPool起到了很重要角色盾计,我們將在下一小節(jié)中解析
四、RealConnection
RealConnection赁遗,描述的是一次與遠(yuǎn)程服務(wù)器的連接署辉,所以它需要具備與遠(yuǎn)程地址進(jìn)行建立連接,通行的能力岩四。這些能里我們可以在后續(xù)它的成員變量和方法中看出來哭尝。照例,我們來看一下的構(gòu)造函數(shù)和成員變量剖煌。
【4.1】成員變量和構(gòu)造
public final class RealConnection extends Http2Connection.Listener implements Connection {
...
private static final int MAX_TUNNEL_ATTEMPTS = 21;
//連接池
public final RealConnectionPool connectionPool;
//路由器
private final Route route;
//這個socket將在connect()方法中被賦值材鹦,并且不會再重新賦值。它用于底層的Socket通信耕姊。
private Socket rawSocket;
//代表著應(yīng)用層的Socket
private Socket socket;
//描述一次完整握手過程的對象桶唐。
private Handshake handshake;
//協(xié)議枚舉類,包括“http/1.0”茉兰、“http/3.1”等尤泽。
private Protocol protocol;
//代表了一個Http2的Socket連接
private Http2Connection http2Connection;
//與服務(wù)器進(jìn)行數(shù)據(jù)交互的流操作對象。
private BufferedSource source;
private BufferedSink sink;
//表示connection的一個標(biāo)志位规脸,被connectionPool管理著坯约,并且一旦為true,將一直為true燃辖。代表著這個連接不需要新的Exchage了鬼店。
boolean noNewExchanges;
...
/** 這個連接所負(fù)載的請求 */
final List<Reference<Transmitter>> transmitters = new ArrayList<>();
...
//構(gòu)造函數(shù)需要連接池和路由器。
public RealConnection(RealConnectionPool connectionPool, Route route) {
this.connectionPool = connectionPool;
this.route = route;
}
總結(jié): 一些主要的成員變量已經(jīng)如上列出注釋黔龟。接下來從它最重要的方法connect()入手來理解它的作用妇智。
【4.2】 connect():與遠(yuǎn)程服務(wù)器建立連接
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
//當(dāng)portacol不等空時,代表連接已經(jīng)建立氏身,拋出錯誤巍棱。
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
//注意這里的ConnectSpec對象,它代表了Http的Socket通信的配置蛋欣,比如它會指定TLS協(xié)議版本航徙。
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
//1.對協(xié)議配置的一些檢查,如果配置不合法將會拋出錯誤
//HTTP的話陷虎,判斷是否配置了不允許明文傳輸或者Android平臺規(guī)定了不允許明文傳輸到踏。不滿足的拋出錯誤杠袱。
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
} else {
//如果是Https連接,判斷是否配置h2_prior_knowledge窝稿。
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
throw new RouteException(new UnknownServiceException(
"H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
}
}
//從這里開始連接楣富。
while (true) {
try {
//2.檢查是否需要隧道模式,如果需要就建立隧道連接伴榔。
//如果目標(biāo)地址是Https協(xié)議纹蝴,但是又通過Http協(xié)議代理的話,將會滿足判定踪少。
if (route.requiresTunnel()) {
//詳見【4.3】
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
//rawSocket為空塘安,代表不能建立隧道連接,退出援奢。
if (rawSocket == null) {
break;
}
} else {
//3.詳見【4.4】建立普通的Socket連接
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
//4. 詳見【4.5】建立協(xié)議兼犯。
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
...
}
}
//5. 對隧道連接建立失敗的處理
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
}
//如果是Http2協(xié)議,獲取最大并發(fā)流限制
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
總結(jié):該方法是Connection處理連接邏輯的地方萝究,主要包括一下幾點:
- 通過protocol來判斷這個連接是否已經(jīng)建立好了免都,如果不為空,就代表已經(jīng)建立好帆竹,此時會拋出錯誤绕娘。
- 根據(jù)不同的Http連接協(xié)議,進(jìn)行配置的檢查栽连。
- 判斷是否建立隧道連接险领,是的話進(jìn)入隧道連接流程。它的判定條件是秒紧,HTTP代理的Http2或者Https绢陌。建立隧道連接的Http代理將不再解析數(shù)據(jù),而是直接轉(zhuǎn)發(fā)數(shù)據(jù)熔恢。
- 建立普通的Socket連接脐湾。
- 建立協(xié)議:TCL握手,HTTP/2的協(xié)商等叙淌。
- 對連接建立后或者失敗的一些處理
【4.3】connectTunnel():建立隧道連接
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
EventListener eventListener) throws IOException {
//1. 創(chuàng)建用于隧道連接用的請求秤掌。
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
//2. 詳見【4.4】和普通連接一樣,也需要進(jìn)行Socket連接
connectSocket(connectTimeout, readTimeout, call, eventListener);
//3. 創(chuàng)建隧道
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
if (tunnelRequest == null) break; // Tunnel successfully created.
// The proxy decided to close the connection after an auth challenge. We need to create a new
// connection, but this time with the auth credentials.
closeQuietly(rawSocket);
rawSocket = null;
sink = null;
source = null;
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
}
}
總結(jié): 創(chuàng)建隧道連接鹰霍,就是在Http代理的代理上建立Https連接闻鉴。主要的做了如下事情:
- 創(chuàng)建一個tunnelRequest:一個通過代理建里TLS隧道的請求。由于是未加密的茂洒,它的頭部信息是只包涵了最小的頭集孟岛,這也是為了避免傳遞一些敏感的數(shù)據(jù)給到Http,比如cookie等。
- 進(jìn)行Socket連接渠羞。
- 進(jìn)行隧道連接:傳入剛剛創(chuàng)建的tunnelRequset斤贰,構(gòu)建出一個Http1ExchangeCodec對象,用于Http1協(xié)議的流操作實現(xiàn)類次询,對Http代理進(jìn)行連接請求腋舌。
【4.4】connectSocket()
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//1. 根據(jù)不同的代理類型來選擇不同的Socket生成策略。
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
//設(shè)置超時
rawSocket.setSoTimeout(readTimeout);
try {
//2. 采用平臺上的連接socket方式
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
...
}
//得到Socket的輸出輸入流
try {
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
總結(jié): 該方法是與遠(yuǎn)程服務(wù)器地址建立起Socket連接渗蟹,并獲得輸入輸出流。具體的如下:
- 如果是直連或者Http類型時赞辩,直接通過SocketFactory新建一個Socket雌芽。否則是代理Socket,將代理傳入Socket新建一個代理Socket辨嗽。
- 建立Socket連接世落。Platform.get()在這里得到的是Android平臺,而它的內(nèi)部做的是也就是
socket.connect(address, connectTimeout);
在這一步connect過后糟需,socket完成了3次握手建立TCP連接屉佳。
- 獲得Socket的輸出輸入流。
【4.5】establishProtocol()
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
//1. 判斷是否為http請求
if (route.address().sslSocketFactory() == null) {
//2.如果http請求里包涵了“h2_prior_knowledge”協(xié)議洲押,代表是一個支持明文的http2請求武花,所以仍然開啟的是http2的連接
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
//3. 建立http2連接
startHttp2(pingIntervalMillis);
return;
}
//4. 不屬于以上情況,正常建立http連接
socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}
eventListener.secureConnectStart(call);
//5. 詳見【4.6】建立Tls協(xié)議
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
//建立http2連接
if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}
總結(jié): 該方法根據(jù)請求協(xié)議杈帐,來確定建立的連接是否需要進(jìn)一步協(xié)議處理体箕。具體的如下:
- 如果是http請求但是包涵“h2_prior_knowledge”或者是http2協(xié)議,都進(jìn)一步構(gòu)建htpp2連接挑童。
- 其他的為正常的http請求累铅,直接將代表底層的rawSocket賦值給應(yīng)用層的socket。
【4.6】connectTls()
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// 1. 將剛剛得到的socket通過sslSocketFactory進(jìn)行包裝
//得到SSLSocket對象站叼。
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// 2. 詳見【4.7】對sslSocket進(jìn)行配置協(xié)議娃兽。
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
//3. 看情況是否進(jìn)行Tls擴(kuò)展配置
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
//4. 開始進(jìn)行三次握手
sslSocket.startHandshake();
SSLSession sslSocketSession = sslSocket.getSession();
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);
//5. 對sslSocket的地址與主機(jī)地址進(jìn)行校驗,確保一致可用尽楔。
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
if (!peerCertificates.isEmpty()) {
X509Certificate cert = (X509Certificate) peerCertificates.get(0);
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
} else {
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified (no certificates)");
}
}
//6. 證書校驗
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
//7. 在3中如果配置了進(jìn)行擴(kuò)展投储,那么在這里將會取到協(xié)議協(xié)商的結(jié)果。
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
//8. 將剛才完成握手和協(xié)議校驗的sslSocket保存起來
//并且獲得用于IO傳輸?shù)膕ource翔试、sink
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
...
} finally {
...
}
}
總結(jié): 在這個方法里轻要,連接將進(jìn)行SSL配置,三次握手垦缅,證書校驗等工作冲泥。具體的如下:
- 將socket包裝成SLLSocket。
- 對SLLSocket進(jìn)行協(xié)議配置。
- 如果有需要凡恍,對SLL協(xié)議進(jìn)行擴(kuò)展配置志秃。
- 開始三次握手。
- 對主機(jī)地址一致性進(jìn)行校驗嚼酝,防止握手過程中丟失浮还。
- 對服務(wù)器回傳回來的證書進(jìn)行合法行校驗。
- 如果需要闽巩,取得握手過程中钧舌,協(xié)議協(xié)商選擇出的協(xié)議。
- 將完成握手和協(xié)議校驗的SSLSocket保存起來涎跨,并獲得用于IO傳輸?shù)膕ource洼冻、sink。
【4.7】configureSecureSocket() 對SSLScoket進(jìn)行協(xié)議配置
ConnectionSpecSelector.java
ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
ConnectionSpec tlsConfiguration = null;
for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
ConnectionSpec connectionSpec = connectionSpecs.get(i);
if (connectionSpec.isCompatible(sslSocket)) {
tlsConfiguration = connectionSpec;
nextModeIndex = i + 1;
break;
}
}
...
Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
return tlsConfiguration;
}
總結(jié): 可以看到隅很,對SSLScoket配置撞牢,就是遍歷connectionSpecs集合,然后挑出適合于這個sslScoket的配置叔营,然后進(jìn)行要用屋彪。具體的如下:
- connectionSpecs集合:在OkHttpClient創(chuàng)建的時候有默認(rèn)值:
OkHttpClient.java
static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
- 協(xié)議的應(yīng)用最終會調(diào)用到ConnectionSpec.apply() 方法,對SSLScoket進(jìn)行tsl版本绒尊,設(shè)置密碼套件畜挥。
【4.8】ConnectionSpec.apply(): 協(xié)議應(yīng)用
ConnectionSpec.java
void apply(SSLSocket sslSocket, boolean isFallback) {
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
if (specToApply.tlsVersions != null) {
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
}
if (specToApply.cipherSuites != null) {
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
}
}
總結(jié): 對這個socket設(shè)置tls版本和密碼套件
【4.9】isEligible():判斷連接是否可復(fù)用的邏輯
RealConnection.java
boolean isEligible(Address address, @Nullable List<Route> routes) {
// 如果這個連接所承載的請求達(dá)到最大,則不能重用
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// 如果不是Host域婴谱,看他們地址是否完全一樣砰嘁。
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// Host域相同,返回可以復(fù)用的結(jié)果勘究。
if (address.url().host().equals(this.route().address().url().host())) {
return true;
}
//下面是Http2連接復(fù)用相關(guān)矮湘。
....
return true;
}
總結(jié): 這個方法在后續(xù)的解析中會涉及到,所以先放在這里講了口糕。主要是用來判斷這個連接可不可以復(fù)用的祭衩。判斷條件如注釋演熟。
五钾唬、ConnectiongPool:連接池
在3.6的findConnetion過程中仓犬,我們看到了很多次連接池的身影,它對連接的復(fù)用也起著絕對重要的位置超棺,如果不仔細(xì)的理解它的話向族,查找連接這塊的邏輯就會少一大快。照例棠绘,從它的出生件相、成員變量和構(gòu)造函數(shù)來初步認(rèn)識它再扭。
【5.1】RealConnection 的出生
OkHttpClient.Builder.java
public Builder() {
...
connectionPool = new ConnectionPool();
}
在Builder()里創(chuàng)建默認(rèn)的連接池。
public final class ConnectionPool {
final RealConnectionPool delegate;
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
}
總結(jié): 可以看出到夜矗,ConectionPool才用代理模式泛范,實際邏輯交給RealConnection()。5個最大空閑連接紊撕,每個連接可卑盏矗活5分鐘。
【5.2】成員變量和構(gòu)造函數(shù)
public final class RealConnectionPool{
//
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** 每個地址可保持的最大空閑連接 */
private final int maxIdleConnections;
//連接的倍苑觯活時間
private final long keepAliveDurationNs;
//連接清理任務(wù)
private final Runnable cleanupRunnable = () -> {
while (true) {
//詳見【5.6】
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
//等待喚醒執(zhí)行清理任務(wù)区赵。
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
//連接集合,采用雙向鏈標(biāo)數(shù)據(jù)結(jié)構(gòu)
private final Deque<RealConnection> connections = new ArrayDeque<>();
//路由數(shù)據(jù)庫
final RouteDatabase routeDatabase = new RouteDatabase();
//清除任務(wù)執(zhí)行標(biāo)志
boolean cleanupRunning;
/**
*構(gòu)造函數(shù)
*/
public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
...
}
}
總結(jié): 可以看出浪南,這個連接池是用來管理同個地址的連接的惧笛。它提供根據(jù)地址查找可用連接、清除連接等功能逞泄。接下來介紹一下它的幾個重要方法。
【5.3】transmitterAcquirePooledConnection():獲取連接
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
【詳見4.9】
if (!connection.isEligible(address, routes)) continue;
【詳見2.2】
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
總結(jié): 遍歷保存的連接拜效,調(diào)用RealConnection.isEligible() 來判斷這個連接是否符合條件喷众。將這個請求的Transmitter登記到RealConnection。
【5.4】connectionBecameIdle()
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
if (connection.noNewExchanges || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
//通知清理任務(wù)執(zhí)行紧憾。
notifyAll();
connection limit.
return false;
}
}
總結(jié): 將一個連接變?yōu)榭臻e連接到千。如果此時這個連接不可用的話,將連接從連接集合中移除赴穗,并返回true憔四。如果還可以,通知清理任務(wù)執(zhí)行般眉,并返回false了赵。
【5.5】put()
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
總結(jié): 該方法是將一個連接放入連接池中,然后執(zhí)行清理任務(wù)甸赃,不過它會被堵塞住柿汛,直到【5.4】方法觸發(fā)。
【5.6】cleanup()
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
//1. 遍歷連接池
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//2. 如果連接還在用埠对,繼續(xù)遍歷
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
//3. 找出最長空閑時間和對于的連接
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//4. 清除空閑最長的連接络断,而且需要滿足如下條件:
//a. 空閑時間大于最大保活時間项玛。
//b. 空閑連接數(shù)大于最大空閑連接數(shù)
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// 清理不了貌笨,返回下次清理需要的時間
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 沒有空閑連接,返回keepAliveDuration時間襟沮,代表keepAliveDuration后再執(zhí)行锥惋。
return keepAliveDurationNs;
} else {
// 沒有空閑或者在用的連接昌腰,清理結(jié)束。
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// 已經(jīng)清理了一個净刮,會立即再執(zhí)行清理任務(wù)剥哑。
return 0;
}
總結(jié): 這是一個清理連接的方法,它做的使其如下:
- 遍歷連接淹父,如果連接還在用跳過株婴。
- 找出最長空閑時間和其連接。
- 如果滿足條件下暑认,將連接移除連接池困介。然后觸發(fā)再執(zhí)行一次清理任務(wù)。
- 如果沒有找到蘸际,返回下次需要清理的時間或者-1代表結(jié)束清理座哩。
小篇結(jié):本篇是介紹OkHttp的網(wǎng)絡(luò)連接建立。開篇先介紹了Trasnmitter這一重要的類粮彤,隨后從ConnectIntercepter入手根穷,深入研究了連接Connection的獲取邏輯。在獲取的過程中导坟,我們將到了連接緩存的處理屿良。當(dāng)獲取不到緩存的時候,便會新建一個全新的網(wǎng)絡(luò)連接惫周,在這個過程中會進(jìn)行Http的3次握手等過程尘惧。在最后2小節(jié)中,分別介紹了在整個過程中的中心類递递,被查找對象:RealConnection喷橙。和管理緩存Connection的ConnectionPool。最后以一張圖來總結(jié)這一過程