public final class ConnectionPool {
/**
* Background threads are used to cleanup expired connections. There will be at most a single
* thread running per connection pool. The thread pool executor permits the pool itself to be
* garbage collected.
* 后台线程用于清理过期的连接。 每个连接池最多只能运行一个线程。 线程池执行器允许池本身被垃圾收集。
*/
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, //线程池,用来检测闲置socket并对其进行清理
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/**
* The maximum number of idle connections for each address.
* 每个地址的最大空闲连接数。
*/
private final int maxIdleConnections;//最大闲置连接数
private final long keepAliveDurationNs;//连接超时时间
private final Runnable cleanupRunnable = new Runnable() {
@Override
public void run() {
while (true) {
//线程中不停调用Cleanup 清理的动作并立即返回下次清理的间隔时间。继而进入wait 等待之后释放锁,继续执行下一次的清理
long waitNanos = cleanup(System.nanoTime());
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) {
}
}
}
}
}
};
//connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();//用来记录连接失败router
boolean cleanupRunning;
/*
* 在遍历缓存列表的过程中,使用连接数目inUseConnectionCount 和闲置连接数目idleConnectionCount 的计数累加值都是
* 通过pruneAndGetAllocationCount() 是否大于0来控制的。那么很显然pruneAndGetAllocationCount() 方法就是用来识别对应连接是否闲置的。
* >0则不闲置。否则就是闲置的连接。
*/
long cleanup(long now) {
int inUseConnectionCount = 0;//使用中的连接数
int idleConnectionCount = 0;//闲置连接数目
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;//用来记录连接池闲置连接中最久闲置时间
// Find either a connection to evict, or the time that the next eviction is due.
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++;//连接还在使用中,使用中连接数+1
continue;
}
idleConnectionCount++;//连接未使用了,闲置连接数+1
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//连接存在时间超过限制、或者限制连接数超过最大闲置连接数,则从连接池中remove掉多余连接
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;
}
}
closeQuietly(longestIdleConnection.socket());//关闭从连接池中移除的连接的socket
// Cleanup again immediately.
return 0;
}
/**
* 因为每次使用连接,都会在连接的allocations里增加一条弱引用数据
* 连接使用完之后弱引用中的对象自然就释放了,如果还有任何一个引用未释放,则说明连接还在使用中
*/
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
for (int i = 0; i < references.size(); ) {//size()就是该连接对应socket被引用的次数
Reference<StreamAllocation> reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
StreamAllocation.StreamAllocationReference streamAllocRef = (StreamAllocation.StreamAllocationReference) reference;
String message = "A connection to " + connection.route().address().url() + " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
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;//连接的allocations计数器中存的弱引用对象都已经释放了
}
}
return references.size();
}
//往连接池中放入新的连接
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);//新的连接放入连接池之前,执行清理闲置连接的线程
}
connections.add(connection);//新的连接放入连接池
}
/*
* 遍历connections缓存列表,当某个连接计数的次数小于限制的大小以及request的地址和缓存列表中此连接的地址完全匹配。
* 则直接复用缓存列表中的connection作为request的连接。
*/
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {//校验连接是否可以复用
/*
* streamAllocation.allocations是个对象计数器,其本质是一个 List<Reference<StreamAllocation>>
* 存放在RealConnection连接对象中用于记录Connection的活跃情况。
*/
streamAllocation.acquire(connection);//会在connection.allocations中添加一个弱引用保存的对象
return connection;
}
}
return null;
}
}
//RealConnection中,校验连接是否可以复用
public boolean isEligible(Address address, @Nullable Route route) {
// If this connection is not accepting new streams, we're done.
if (allocations.size() >= allocationLimit || noNewStreams) return false;//连接调用次数没有超过限制
// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;//比较dns、代理证书、协议、端口等信息
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().host())) {//比较host
return true; // This connection is a perfect match.
}
// At this point we don't have a hostname match. But we still be able to carry the request if
// our connection coalescing requirements are met. See also:
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. This connection must be HTTP/2.
if (http2Connection == null) return false;
// 2. The routes must share an IP address. This requires us to have a DNS address for both
// hosts, which only happens after route planning. We can't coalesce connections that use a
// proxy, since proxies don't tell us the origin server's IP address.
if (route == null) return false;
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
// 3. This connection's server certificate's must cover the new host.
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 4. Certificate pinning must match the host.
try {//证书校验
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // The caller's address can be carried by this connection.
}
OkHttpClient->Internal.instance->equalsNonHost(Address a, Address b)->Address.equalsNonHost()
boolean equalsNonHost(Address that) {
return this.dns.equals(that.dns)
&& this.proxyAuthenticator.equals(that.proxyAuthenticator)
&& this.protocols.equals(that.protocols)
&& this.connectionSpecs.equals(that.connectionSpecs)
&& this.proxySelector.equals(that.proxySelector)
&& equal(this.proxy, that.proxy)
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.certificatePinner, that.certificatePinner)
&& this.url().port() == that.url().port();
}
3、获取连接池中闲置连接 RealCall.java
final class RealCall implements Call {
...
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);//请求塞到队列
Response result = getResponseWithInterceptorChain();//这里面有添加一些拦截器
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//这些是调用处传入的拦截器
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));//连接拦截器,这里面有去从连接池中获取闲置连接
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
...
}
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);//这里去连接池
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
public final class StreamAllocation {
//去连接池拿连接
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {//findHealthyConnection会从连接池中拿到可用连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
}
总结:连接池复用流程
ConnectInterceptor.intercept(..) -> StreamAllocation.newStream(..) -> StreamAllocation.findHealthyConnection(..)
->findConnection(..) -> Internal.instance.get(connectionPool, address, this, null) ->
OkHttpClient.Internal.instance.get(..)
最终调用的方法:
@Override
public RealConnection get(ConnectionPool pool, Address address, StreamAllocation streamAllocation, Route route) {
return pool.get(address, streamAllocation, route);//遍历连接池,取出合适的(一系列校验)连接
}
补充:在findConnection()中有执行connect(),内部创建了socket,在findConnection()末尾关闭socket,
http请求本来就是短连接,用完后就是要关闭的!
参考资料