依旧再看一下ConnectInterceptor的intercept方法:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;//已经创建的OkHttpClient对象
//构造器中传入OkHttpClient对象
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;//上一个拦截器中创建的拦截器链
Request request = realChain.request();//前面传过来的Request对象
StreamAllocation streamAllocation = realChain.streamAllocation();//StreamAllocation对象,这个对象,已经在RetryAndFollowUpInterceptor中创建。
// We need the network to satisfy this request. Possibly for validating a conditional GET.
//如果不是GET方法,需要做额外的状态检测
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//创建一个新的Http Stream。用来编码HTTP requests和解码HTTP responses
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取一个RealConnection,即网络连接,可以从连接池中获取,也可以创建一个新的链接。
RealConnection connection = streamAllocation.connection();
//执行下一个拦截器,也就是CallServerInterceptor,进行网络请求和结果传递操作
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
上面代码可以看出,intercept方法的流程有:
获取Request,StreamAllocation对象;
创建一个新流HttpCodec,用来编码HTTP requests和解码HTTP responses;
创建一个新的网络连接RealConnection;
继续执行下一个拦截器,推动拦截器链的执行。
根据上面的流程,看一下代码执行:
//RetryAndFollowUpInterceptor
public final class RetryAndFollowUpInterceptor implements Interceptor {
......
private StreamAllocation streamAllocation;
@Override public Response intercept(Chain chain) throws IOException {
......
//创建StreamAllocation对象
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
}
}
//StreamAllocation
public final class StreamAllocation {
public final Address address;
private RouteSelector.Selection routeSelection;
private Route route;
private final ConnectionPool connectionPool;
public final Call call;
public final EventListener eventListener;
private final Object callStackTrace;
// State guarded by connectionPool.
private final RouteSelector routeSelector;
//上面的属性在StreamAllocation构造其中初始化
private int refusedStreamCount;
private RealConnection connection;
private boolean reportedAcquired;
private boolean released;
private boolean canceled;
private HttpCodec codec;
//构造器
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对象,参数有:
连接池,在OkHttpClient中创建;
Address对象,通过Request的URL内容创建Address对象;
Call对象,前面的RealCall对象;
需要到StreamAllocation类中查看newStream方法:
StreamAllocation#newStream
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//创建新的Http Stream
//传入OkHttpClient对象,和拦截器链,是否需要对连接进行额外检测
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();//连接超时时间
int readTimeout = chain.readTimeoutMillis();//读超时时间
int writeTimeout = chain.writeTimeoutMillis();//写超时时间
boolean connectionRetryEnabled = client.retryOnConnectionFailure();//连接失败是否可以重试,用户可以自己配置
try {
//调用findHealthyConnection方法,查找一个状态良好的网络连接,有可能是从连接池中获取到的,或者直接创建了一个新的,并添加到连接池中。
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
//基于获取到的RealConnection对象,创建一个新的HttpCodec对象。
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
//返回创建的HttpCodec对象。
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
上面代码流程为:
获取一个状态良好的网络连接对象;
基于上面的网络连接对象,创建一个新的Http流对象HttpCodec;
返回创建的HttpCodec对象。
再看一下是如何获取一个获取一个状态可用的连接的:
StreamAllocation#findHealthyConnection
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
//查找一个可用的连接。一直找到一个可用的连接这个过程才会停止。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
//无限循环,直到获取一个可用的连接
while (true) {
//调用findConnection方法,查找一个链接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
//RealConnection的成员属性successCount表示成功传输Http流的次数,如果为0,表示这是一个新的连接。可以不用在进行范围更广的状态监测。
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.
//进行更广范围的状态监测,如果当前链接的状态已经不可用,
//调用noNewStreams()方法,标记RealConnection的noNewStreams为false,表示这个连接不再创建新的Http流。
//接下来请回清除这个连接。
//继续下一个循环
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
//当前连接RealConnection可用,返回使用
return candidate;
}
}
上述代码的执行流程如下:
开始while循环,无限循环的查找一个连接
调用findConnection获取连接;
判断查找到的这个RealConnection对象是不是新的连接,通过successCount是否为0来判断。如果是新的连接,直接返回使用这个连接,不用再往下执行状态检测;
对这个连接RealConnection进行状态检测:
如果这个连接的状态不可用,调用noNewStreams()方法,标记RealConnection的noNewStreams为false,表示这个连接不再创建新的Http流;
继续下一个循环,查找下一个链接。
返回状态可用的连接RealConnection对象。
下面继续看一下findConnection方法是什么流程:
StreamAllocation#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,
boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");//资源已经被释放
if (codec != null) throw new IllegalStateException("codec != null");//codec不为空,说明当前有正在处理的流
if (canceled) throw new IOException("Canceled");//该请求已经被取消
// 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 streams.
//先尝试使用之前已经分配的链接,但是这个链接有可能已经不能分配新的Http流
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();//如果当前连接已经不能分配新的流,就释放资源,并返回当前连接的Socket对象,并会把this.connection置为null。
if (this.connection != null) {//this.connection依旧不为空,说明这个connection可用
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
//this.connection为null,就需要从连接池中获取链接
if (result == null) {
// Attempt to get a connection from the pool.
//获取链接
Internal.instance.get(connectionPool, address, this, null);
//找到了一个连接
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
//关闭前面的Socket
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();
}
synchronized (connectionPool) {
if (canceled) 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地址,再次尝试从连接池中查找链接。
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
//查找链接
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
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.
//经过上面的查找,没有能够从连接池中查找到合适的链接。就需要创建一个新的RealConnection对象。
route = selectedRoute;
refusedStreamCount = 0;
//创建新的RealConnection对象。
result = new RealConnection(connectionPool, selectedRoute);
//调用acquire方法,表示就是要使用这个链接
acquire(result, false);
}
}
// 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链接,并开始进行handshakes过程,
result.connect(
connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
//更改路由信息数据库
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
//需要将新创建的RealConnection放入到连接池中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
//如果同时创建了到同一地址的另一个多路复用连接,则释放此连接并获取该连接。
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
//返回连接
return result;
}
代码比较长,上述代码流程如下:
如果已经分配过了连接,即this.connection不为null。先尝试使用这个连接,但是这个连接有可能已经不能创建新的Http Stream;
如果不能使用上面的连接,就需要释放这个链接,调用releaseIfNoNewStreams;如果可用,就直接返回这个链接使用;
尝试在连接池中查找可用的链接,调用get方法;
如果在连接池中没有找到,更改路由配置,再次从连接池中查找,调用get方法;
如果依旧没有找到,就需要创建一个新的RealConnection对象,开始建立TCP连接,调用connect方法;调用acquire方法表示使用这个RealConnection对象;
将新创建的RealConnection对象加入到连接池中,调用put方法;
如果当前针对这个地址的链接是一个多路复用的连接,需要去重,只保留一个连接就够用了,调用deduplicate方法。
我们已经获取了可用的链接,返回即可。
所以,我们需要在深入到上面提到的方法中,了解更多的细节:
releaseIfNoNewStreams方法;
连接池get方法;
acquire方法;
连接池put方法;
deduplicate方法;
StreamAllocation#releaseIfNoNewStreams–deallocate
/**
* Releases the currently held connection and returns a socket to close if the held connection
* restricts new streams from being created. With HTTP/2 multiple requests share the same
* connection so it's possible that our connection is restricted from creating new streams during
* a follow-up request.
*/
//如果这个connection对象已经不能创建新的streams,就需要释放持有的connection对象,并把这个connection的socket对象返回前面去关闭它;
//由于HTTP/2多路请求可以共享同一个连接connection对象,所以有可能后续的请求在这个链接上不能创建新的Stream。
private Socket releaseIfNoNewStreams() {
assert (Thread.holdsLock(connectionPool));
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && allocatedConnection.noNewStreams) {
return deallocate(false, false, true);//解除分配
}
return null;
}
/**
* Releases resources held by this allocation. If sufficient resources are allocated, the
* connection will be detached or closed. Callers must be synchronized on the connection pool.
*/
//释放这个分配占用的资源。
//解除分配
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
assert (Thread.holdsLock(connectionPool));
if (streamFinished) {
this.codec = null;
}
if (released) {
this.released = true;
}
Socket socket = null;
if (connection != null) {
if (noNewStreams) {
connection.noNewStreams = true;
}
if (this.codec == null && (this.released || connection.noNewStreams)) {
release(connection);//释放connection
if (connection.allocations.isEmpty()) {//如果当前connection的分配记录列表为空了,表示没有流占用了
connection.idleAtNanos = System.nanoTime();
//connection置为空闲状态,调用connectionPool的connectionBecameIdle方法
if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
socket = connection.socket();//获取这个连接的socket对象
}
}
connection = null;//StreamAllocation的connection属性置为null
}
}
return socket;
}
/** Remove this allocation from the connection's list of allocations. */
//connection.allocations表示当前connection被分配的记录
private void release(RealConnection connection) {
for (int i = 0, size = connection.allocations.size(); i < size; i++) {
Reference<StreamAllocation> reference = connection.allocations.get(i);
if (reference.get() == this) {//是否是这个分配记录
connection.allocations.remove(i);//从列表中删除
return;
}
}
throw new IllegalStateException();
}
来看一下ConnectionPool的connectionBecameIdle方法:
ConnectionPool#connectionBecameIdle
/**
* Notify this pool that {@code connection} has become idle. Returns true if the connection has
* been removed from the pool and should be closed.
*/
//通知连接池这个connection变成空闲状态。如果连接connection被删除了且被关闭了就返回true。
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
if (connection.noNewStreams || maxIdleConnections == 0) {
connections.remove(connection);//从列表中删除这个链接
return true;
} else {
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
return false;
}
}
ConnectionPool#get
/**
* Returns a recycled connection to {@code address}, or null if no such connection exists. The
* route is null if the address has not yet been routed.
*/
//根据Address返回一个循环使用的链接,如果没有返回null
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
//循环遍历连接池中的connections
for (RealConnection connection : connections) {
//判断是否符合条件。判断Address内容中非主机部分和主机部分是否都符合
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);//connection符合条件,使用这个connection,并且添加connection的分配记录。
return connection;//返回这个connection。
}
}
return null;
}
RealConnection#isEligible
//如果这个连接可以承载一个这个address对应的Http 流,就返回true,代表这个这个连接可用
public boolean isEligible(Address address, @Nullable Route route) {
// If this connection is not accepting new streams, we're done.
//如果当前链接被分配次数allocations已经达到最高次数,或者noNewStreams = true,都表示这个链接不能再分配新的流了。
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// If the non-host fields of the address don't overlap, we're done.
//如果地址Address内容的非主机部分不匹配,表示这个连接也不能使用
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// If the host exactly matches, we're done: this connection can carry the address.
//主机部分匹配的话,就返回true
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
......
}
StreamAllocation#acquire
/**
* Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
* {@link #release} on the same connection.
*/
//使用这个分配的connection。
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
this.reportedAcquired = reportedAcquired;
//这个链接的allocations添加StreamAllocation。StreamAllocationReference是WeakReference子类
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
acquire方法其实就是connection的allocations分配记录中添加一条分配记录。
ConnectionPool#put
//没有从连接池中获取所需要的RealConnection对象,创建了一个新的RealConnection对象,放入到连接池中
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//cleanupRunning表示清理链接的任务是否正在进行中。如果没在进行中,需要先重新开始清理任务
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
//放入到连接池中
connections.add(connection);
}
ConnectionPool#deduplicate
/**
* Replaces the connection held by {@code streamAllocation} with a shared connection if possible.
* This recovers when multiple multiplexed connections are created concurrently.
*/
//当多个可以多路复中的链接同时被创建,需要去除重复的链接
@Nullable Socket deduplicate(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {//循环遍历
if (connection.isEligible(address, null)
&& connection.isMultiplexed()
&& connection != streamAllocation.connection()) {
return streamAllocation.releaseAndAcquire(connection);
}
}
return null;
}
RealConnection#isMultiplexed
/**
* Returns true if this is an HTTP/2 connection. Such connections can be used in multiple HTTP
* requests simultaneously.
*/
//HTTP/2 connection可以作为多路复用的链接
public boolean isMultiplexed() {
return http2Connection != null;
}
StreamAllocation#releaseAndAcquire
/**
* Release the connection held by this connection and acquire {@code newConnection} instead. It is
* only safe to call this if the held connection is newly connected but duplicated by {@code
* newConnection}. Typically this occurs when concurrently connecting to an HTTP/2 webserver.
*
* Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion
* of the synchronized block. (We don't do I/O while synchronized on the connection pool.)
*/
public Socket releaseAndAcquire(RealConnection newConnection) {
assert (Thread.holdsLock(connectionPool));
if (codec != null || connection.allocations.size() != 1) throw new IllegalStateException();
// Release the old connection.
//删除旧的connection
Reference<StreamAllocation> onlyAllocation = connection.allocations.get(0);
Socket socket = deallocate(true, false, false);
// Acquire the new connection.
//使用新的链接
this.connection = newConnection;
newConnection.allocations.add(onlyAllocation);
return socket;
}
还需要再回到StreamAllocation#findHealthyConnection方法中。上述这么多方法会返回一个可用的RealConnection对象,在findHealthyConnection中还需要在进行isHealthy方法进行状态检测:
/** Returns true if this connection is ready to host new streams. */
public boolean isHealthy(boolean doExtensiveChecks) {
//如果socket已经被关闭,或者socket的输入流关闭,如果socket的输出流被关闭,都表示这个Connection不可用了
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
//如果是http2Connection,判断是否Shutdown
if (http2Connection != null) {
return !http2Connection.isShutdown();
}
//做额外的检测
if (doExtensiveChecks) {
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
if (source.exhausted()) {
return false; // Stream is exhausted; socket is closed.
}
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
// Read timed out; socket is good.
} catch (IOException e) {
return false; // Couldn't read; socket is closed.
}
}
return true;
}
上述代码主要是判断当前的链接状态是否可用:
socket是否关闭,socket的输入/输出流是否关闭;
如果是http2Connection,判断是否shutdown;
还需要再做额外的检测;
如果上述isHealthy返回false,则会执行noNewStreams方法,主要是释放资源,并将RealConnection的noNewStreams属性置为true,表示当前connection不在创建新的流。
再通过上面的链接状态监测,我们真正获得了一个可以使用的RealConnection对象。接下来,回到StreamAllocation#newStream方法中,还需要继续看一下:
RealConnection#newCodec
//创建HttpCodec对象
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {//HTTP/2协议下的链接
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {//HTTP/1协议下的链接
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
这样,再回到ConnectInterceptor的intercept方法,我们就执行到了realChain.proceed方法,就执行到了写一个拦截器CallServerInterceptor。
为了更清晰的了解这个流程,画个流程图:
其实还有一些结论:
一个StreamAllocation对应着一个HttpCodec对象,StreamAllocation可以根据Http流的状态来操作HttpCodec对象;即StreamAllocation负责创建和销毁HttpCodec对象;
StreamAllocation负责将HttpCodec和RealConnection对象绑定;
StreamAllocation负责在流传输完成后,也就是read Response的操作完成后,进行StreamFinish的操作,即进行资源的回收。这将驱动RealConnection和ConnectionPool进行资源清理;
下面我们看一下,流传输完成后,如何进行资源清理工作: