该系列OkHttp源码分析基于OkHttp3.14.0版本
该拦截器负责建立与服务器的连接,但是并不与服务器进行IO交互,IO交互是CallServerInterceptor
的职责。生成了一个Exchange
类。
对于Exchange
这个类的而言,我将其理解为一个包含了如何处理Http编码与解码的类,也是由它来指定使用的HTTP协议版本。
在该拦截器的intercept
方法中,可以看到关键的一句代码:
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
进入这个newExchange()
看看:
/** Returns a new exchange to carry a new request and response.
* 返回一个新的Exchange以携带新的请求和响应。 */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) throw new IllegalStateException("released");
if (exchange != null) throw new IllegalStateException("exchange != null");
}
// 用于编码和解码http协议的
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;
}
}
可以看到,这个方法的关键点在于exchangeFinder.find()
这个方法中,进去:
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...省略部分代码
try {
//找到或创建一个连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
...省略部分代码
}
对于这个方法,关键在于生成了一个连接RealConnection
,然后由这个连接作为参数生成了一个ExchangeCodec
,而生成连接的方法在findHealthyConnection
中。那么我们继续跟进:
while (true) {
//生成或复用一个连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
// 如果这是一个全新的连接,我们可以跳过广泛的运行状况检查。
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
// 进行(可能很慢)检查以确认池中连接仍然良好。 如果不是,请将其从池中取出并重新开始。
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
一个while(true)
死循环,调用了findConnection()
,根据名称很容易可以猜到,这里应该就是最终生成或复用连接的地方了。继续跟进:
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
* 返回用于托管新流的连接。 如果存在现有连接,则首选现有连接,然后是池,最后建立一个新连接。
*/
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) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt. 新一次尝试
Route previousRoute = retryCurrentRoute()
? transmitter.connection.route()
: null;
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
// 尝试使用已分配的连接。 我们在这里需要小心,因为我们已经分配的连接可能受到限制,无法创建新的交换。
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
// 我们已经分配了一个连接,很好。
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
// 没有已分配的链接,尝试从链接池冲找一个
// Attempt to get a connection from the pool.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else {
selectedRoute = previousRoute;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
// 如果我们发现一个已经分配或连接的连接,就完成了。
return result;
}
// If we need a route selection, make one. This is a blocking operation.
// 如果需要路线选择,请选择一个。 这是一个阻塞操作。
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) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
// 现在我们有了一组IP地址,请再次尝试从池中获取连接。 由于连接合并,这可能匹配。
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
// 创建一个连接,并将其立即分配给该分配。 这使得异步cancel()可以中断我们将要进行的握手。
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
// 如果我们在第二次发现池化连接,就完成了。
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
// 执行TCP + TLS握手。 这是一个阻塞操作。
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
// 最后一次尝试进行连接合并,只有在我们尝试到同一主机的多个并发连接时才会发生。
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
// 我们输了比赛! 关闭我们创建的连接并返回池连接。
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
代码比较长,基本逻辑为寻找一个可复用的连接,如果找到了就返回,没找到则新建一个连接并放入连接池然后返回。
连接获取到了之后,下一步就是构造一个ExchangeCodec
对象了。那么ExchangeCodec
是用来干啥的呢?根据官方注释:
Encodes HTTP requests and decodes HTTP responses
编码HTTP请求并解码HTTP响应
里面封装了由Okio实现的流读写,利用它,我们就可以操作向服务器写数据以及读取服务器返回的数据。
进入newCodec()
方法,我们可以看到,会返回HTTP1和HTTP2两个不同的ExchangeCodec
,具体返回谁的话是根据http2Connection
是否为null来判断的。
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);
}
}
http2Connection
是否为null
是根据协议来进行判断的,具体的话这里就不展开了,主要涉及几个方法startHttp2()
、establishProtocol()
、connect()
。
现在有了ExchangeCodec
对象后回到我们的newExchange()
方法中,有了ExchangeCodec
后,我们实例化了一个新的Exchange
对象,该对象可以理解为一个工具人,里面包含了一个请求的所有信息。
然后将这个Exchange
对象交给后续的拦截器去和服务器进行数据的读写。