主要的类包含ConnectInterceptor
,StreamAllocation
,RealConnection
,ConnectionPool
。
我们先分析ConnectionPool
,然后分析ConnectionInterceptor
。
ConnectionPool
作用主要是复用Http
连接,避免网络连接的时延,以及避免TCP调谐带来的带宽过小的问题。ConnectionPool
主要有两个方法
get
方法用于获取连接。put
方法用于添加连接。public final class ConnectionPool {
private final Deque<RealConnection> connections = new ArrayDeque<>();
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
}
遍历连接池的连接,address
和route
作为参数调用RealConnection
对象的方法isEligible
来判断连接是否合适。
如果合适,就调用StreamAllocation
的acquire
方法,然后返回RealConnection
对象。当所有的连接都不合适的时候返回null
。
我们来看一下StreamAllocation
的acquire
方法。
public final class 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));
}
}
先判断StreamAllocation
的变量connection
是否为null
。不为null
,说明已经包含一个连接了,直接抛出状态异常。
最重要的创建一个StreamAllocationReference
对象,StreamAllocationReference
是StreamAllocation
的弱引用。然后添加到RealConnection
对象connection
的allocations
变量中。这样就可以遍历RealConnection
的allocations
变量来查看是否被StreamAllocation
使用中。
public final class ConnectionPool {
private final Deque<RealConnection> connections = new ArrayDeque<>();
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
}
put
方法比较简单,先是判断cleanupRunning
,cleanupRunning
表示清理连接的工作正在执行。如果没有执行就调用线程池来执行cleanupRunnable
。然后是把RealConnection
对象connection
添加到变量connections
中。
public final class ConnectionPool {
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
//通过调用cleanup方法来获取等待时间。
long waitNanos = cleanup(System.nanoTime());
//等待时间是-1直接返回
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
//设置等待时间。
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
}
cleanUp
方法会返回三个值,一个是-1直接返回,一个等于0执行循环继续清理,大于0就设置清理的等待时间。
我们来分析一下cleanUp
方法。
返回值的意思:
-1
表示当前没有连接0
表示需要继续清理工作0
表示等待的时长public final class ConnectionPool {
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
......
}
}
先是定义一些变量。
inUseConnectionCount
表示使用中的连接数量。idleConnectionCount
表示空闲的连接数量。longestIdleConnection
表示空闲时间最长的连接。longestIdleDurationNs
表示最长空闲的时间。public final class ConnectionPool {
long cleanup(long now) {
......
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
......
}
}
}
循环遍历connections
,调用pruneAndGetAllocationCount
获取RealConnection
对象的StreamAllocation
数量,当大于0
时,说明存在RealConnection
正在使用中,增加inUseConnectionCount
。如果为0
说明RealConnection
对象已经空闲,增加idleConnectionCount
。然后计算连接的空闲的时长(connection.idleAtNanos表示连接空闲的时间点)。当空闲时长大于当前的最大值longestIdleDurationNs
时,我们赋值longestIdleDurationNs
和longestIdleConnection
。
public final class ConnectionPool {
long cleanup(long now) {
......
synchronized (this) {
......
//说明是连接的空闲时长大于保活的时长或者是空闲数量大于最大的空闲的连接数时,
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
//移除连接,后面关闭它。
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
//返回等待的时间。
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
//连接都在使用中,直接返回保活的时长
return keepAliveDurationNs;
} else {
//当前没有连接,直接返回-1。
cleanupRunning = false;
return -1;
}
}
//关闭最大空闲时长的连接,并返回0,表示继续clean。
closeQuietly(longestIdleConnection.socket());
return 0;
}
}
上述代码已经注释,根据不同的条件,做响应的处理。
我们接下来分析一下pruneAndGetAllocationCount
方法的逻辑。
public final class ConnectionPool {
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}
}
RealConnection
会弱引用StreamAllocation
对象。pruneAndGetAllocationCount
就是遍历弱引用是否存在对象。如果存在就说明RealConnection
在使用,而没有话的就说明已经不在使用了。
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
这段代码会缩短连接的存活时间,使用的是当前时间减去存活时长,就说明当前时间点就可以回收。与存活的策略是有冲突的。
public final class ConnectInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//调用StreamAllocation的newStream方法
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取StreamAllocation到RealConnection对象
RealConnection connection = streamAllocation.connection();
//进行下一级的处理
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
ConnectInterceptor
先是获取到RetryAndFollowUpInterceptor
创建的StreamAllocation
。
接着
StreamAllocation
对象的newStream
方法获取到HttpCodec
对象。StreamAllocation
的connection
的方法。Chain
的proceed
的方法进行处理。也就是传递给下一个拦截器。下面一一分析
newStream
方法StreamAllocation
的newStream
方法比较多,分段阅读。
public final class StreamAllocation {
public HttpCodec newStream(
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();
......
}
读取超时时间等信息。
public final class StreamAllocation {
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
......
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
}
主要是两个功能
findHealthyConnection
方法获取连接,RealConnection
的newCodec
方法获取对象,然后返回。我们来分析一下StreamAllocation
的findHealthyConnection
方法来获取RealConnection
对象。
public final class StreamAllocation {
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
//successCount为0说明连接没使用,因为每次使用后都会增加successCount
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//判断当前的连接是否健康
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
}
循环的调用findConnection
方法获取RealConnection
对象。然后判断RealConnection
对象的successCount
为0
,如果为0
说明RealConnection
未使用,直接返回。不为0
需要通过isHealthy
方法判断当前的RealConnection
是否是健康,主要是判断Socket
是否关闭,以及流是否关闭。
StreamAllocation
的findConnection
的方法比较多,我们只看关键的代码
public final class StreamAllocation {
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
Connection releasedConnection;
synchronized (connectionPool) {
//容错处理
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
//连接为空,尝试
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;
}
......
}
}
先是做一些容错处理,然后是调用Internal
的get
方法来从连接池connectionPool
中获取连接。如果获取到result
不为null
,就直接返回。
public final class StreamAllocation {
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
//通过Route从连接池中获取连接对象
if (newRouteSelection) {
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();
}
route = selectedRoute;
refusedStreamCount = 0;
//创建RealConnection对象
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
}
根据Route
来再一次从连接池connectionPool
中获取对象。如果获取不到,就会创建一个RealConnection
对象。
public final class StreamAllocation {
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
if (foundPooledConnection) {
return result;
}
.......
}
如果从连接池中获取到了连接对象,foundPooledConnection
为true
,就直接的返回了。
public final class StreamAllocation {
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
if (foundPooledConnection) {
return result;
}
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
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;
}
foundPooledConnection
为false
的说明没有从连接池中获取到连接对象,那就是新创建的连接。我们需要调用RealConnection
的connect
进行连接。然后是调用Internal
对象的put
方法把连接加入到连接池中,最后返回。
接着是分析RealConnection
的newCodec
方法。
public final class RealConnection extends Http2Connection.Listener implements Connection {
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
}
主要是创建HttpCodec
。HttpCodec
用于编码请求,解码响应。
public final class StreamAllocation{
public synchronized RealConnection connection() {
return connection;
}
}
直接返回StreamAllocation
的connection
变量。