1.先从构建OkHttpClient说起
重点看下ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MINUTES);
public class OkHttpUtil {
private static final Logger logger = LoggerFactory.getLogger(OkHttpClient.class);
private static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8";
private static final String CHAR_NULL = "";
private static class OkHttpUtilHolder {
private static final OkHttpUtil INSTANCE = new OkHttpUtil();
}
public static OkHttpUtil getIntance() {
return OkHttpUtilHolder.INSTANCE;
}
private OkHttpUtil() {
}
private static OkHttpClient okHttpClient;
private static boolean okHttpLogSwitch;
static OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
static {
int maxIdleConnections = PropertiesUtil.getProperty(OKHTTP_POOL_MAXIDLECONNECTIONS) != null ? Integer.parseInt(PropertiesUtil.getProperty(OKHTTP_POOL_MAXIDLECONNECTIONS)) : DEFAULT_CLIENT_MAXIDLECONNECTIONS;
long keepAliveDuration = PropertiesUtil.getProperty(OKHTTP_POOL_KEEPALIVEDURATION) != null ? Long.parseLong(PropertiesUtil.getProperty(OKHTTP_POOL_KEEPALIVEDURATION)) : DEFAULT_CLIENT_KEEPALIVEDURATION;
long connectTimeout = PropertiesUtil.getProperty(OKHTTP_CLIENT_CONNECTTIMEOUT) != null ? Long.parseLong(PropertiesUtil.getProperty(OKHTTP_CLIENT_CONNECTTIMEOUT)) : DEFAULT_CLIENT_CONNECTTIMEOUT;
long writeTimeout = PropertiesUtil.getProperty(OKHTTP_CLIENT_WRITETIMEOUT) != null ? Long.parseLong(PropertiesUtil.getProperty(OKHTTP_CLIENT_WRITETIMEOUT)) : DEFAULT_CLIENT_WRITETIMEOUT;
long readTimeout = PropertiesUtil.getProperty(OKHTTP_CLIENT_READTIMEOUT) != null ? Long.parseLong(PropertiesUtil.getProperty(OKHTTP_CLIENT_READTIMEOUT)) : DEFAULT_CLIENT_READTIMEOUT;
int maxRetry = PropertiesUtil.getProperty(OKHTTP_CLIENT_MAXRETRY) != null ? Integer.parseInt(PropertiesUtil.getProperty(OKHTTP_CLIENT_MAXRETRY)) : DEFAULT_CLIENT_MAXRETRY;
okHttpLogSwitch = PropertiesUtil.getProperty(OKHTTP_CLIENT_LOGSWITCH) != null ? Boolean.parseBoolean(PropertiesUtil.getProperty(OKHTTP_CLIENT_LOGSWITCH)) : DEFAULT_CLIENT_LOGSWITCH;
boolean logParamsSwitch = PropertiesUtil.getProperty(OKHTTP_CLIENT_LOGPARAMSSWITCH) != null ? Boolean.parseBoolean(PropertiesUtil.getProperty(OKHTTP_CLIENT_LOGPARAMSSWITCH)) : DEFAULT_CLIENT_LOGSWITCH;
boolean enableHttps = PropertiesUtil.getProperty(OKHTTP_CLIENT_ENABLEHTTPS) != null ? Boolean.parseBoolean(PropertiesUtil.getProperty(OKHTTP_CLIENT_ENABLEHTTPS)) : DEFAULT_CLIENT_ENABLEHTTPS;
String timeUnit = PropertiesUtil.getProperty(OKHTTP_CLIENT_TIMEUNIT);
ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MINUTES);
builder.connectionPool(connectionPool)
.retryOnConnectionFailure(true)
.addInterceptor(new RetryIntercepter(maxRetry, okHttpLogSwitch, logParamsSwitch));
if (timeUnit != null && timeUnit.equals(DEFAULT_CLIENT_TIMEUNIT_MS)) {
builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
} else {
builder.connectTimeout(connectTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS);
}
if (enableHttps) {
trustAllHosts();//璁剧疆蹇界暐瀹夊叏璇佷功楠岃瘉
// 瀹炵幇HostnameVerifier鎺ュ彛锛屼笉杩涜url鍜屾湇鍔″櫒涓绘満鍚嶇殑楠岃瘉銆�
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
okHttpClient = builder.build();
}
public OkResponseResult doGet(OkHttpRequest okHttpRequest) {
Headers.Builder headerBuilder = buildHeaders(okHttpRequest.headerParams(), okHttpRequest.retryTimes(), okHttpRequest.reqLogIgnore());
long startNanoTime = nanoTime();
boolean success = true;
String tag = swapTag(okHttpRequest.tag());
Request request = new Request.Builder().headers(headerBuilder.build()).url(okHttpRequest.url()).tag(tag).build();
Call call = okHttpClient.newCall(request);
Response execute = null;
try {
execute = call.execute();
if (execute != null && execute.isSuccessful()) {
return OkResponseResult.buildSuccessResponse(execute);
}
success = false;
return OkResponseResult.buildFaliureResponse(execute);
} catch (Exception e) {
logger.error(GET_ERROR_MSG, e);
if (execute != null && execute.body() != null) {
execute.body().close();
}
success = false;
return OkResponseResult.buildIOExceptionResponse();
} finally {
long micros = NANOSECONDS.toMicros(nanoTime() - startNanoTime);
PerfUtils.perf(success ? "http.success" : "http.fail", getBizName(), getIdentity(okHttpRequest))
.micros(micros)
.logstashOnly();
}
}
}
2.RealCall类是OkHttp的核心类
Call call = okHttpClient.newCall(request);
execute = call.execute();
final class RealCall implements Call {
final OkHttpClient client;
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
/**
* There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward.
* This will be set after we create the call instance then create the event listener instance.
*/
private EventListener eventListener;
/** The application's original request unadulterated by redirects or auth headers. */
final Request originalRequest;
final boolean forWebSocket;
// Guarded by this.
private boolean executed;
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
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.eventListener = client.eventListenerFactory().create(call);
return call;
}
@Override public Request request() {
return originalRequest;
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
private void captureCallStackTrace() {
Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
}
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
@Override public void cancel() {
retryAndFollowUpInterceptor.cancel();
}
@Override public synchronized boolean isExecuted() {
return executed;
}
@Override public boolean isCanceled() {
return retryAndFollowUpInterceptor.isCanceled();
}
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
@Override public RealCall clone() {
return RealCall.newRealCall(client, originalRequest, forWebSocket);
}
StreamAllocation streamAllocation() {
return retryAndFollowUpInterceptor.streamAllocation();
}
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
/**
* Returns a string that describes this call. Doesn't include a full URL as that might contain
* sensitive information.
*/
String toLoggableString() {
return (isCanceled() ? "canceled " : "")
+ (forWebSocket ? "web socket" : "call")
+ " to " + redactedUrl();
}
String redactedUrl() {
return originalRequest.url().redact();
}
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
}
RealCall中重点看一下execute()方法。
client.dispatcher().executed(this) dispatcher是一个分发器,同步请求直接使用当前线程执行操作,异步请求会把任务交给线程池来处理。
Response result = getResponseWithInterceptorChain();最最核心的操作全交给了责任链来处理,这个责任链代码比较巧妙
3.RealInterceptorChain
粗看,还以为RealInterceptorChain的使用有bug,何时跳出责任链?index增长到何时?细看这一个chain,发现对最后一个Interceptor的实现有要求,最后一个Interceptor应该做终止操作,不能允许继续往后传。细节查看CallServerInterceptor代码。
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
@Override public Connection connection() {
return connection;
}
@Override public int connectTimeoutMillis() {
return connectTimeout;
}
@Override public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, millis, readTimeout, writeTimeout);
}
@Override public int readTimeoutMillis() {
return readTimeout;
}
@Override public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, millis, writeTimeout);
}
@Override public int writeTimeoutMillis() {
return writeTimeout;
}
@Override public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, readTimeout, millis);
}
public StreamAllocation streamAllocation() {
return streamAllocation;
}
public HttpCodec httpStream() {
return httpCodec;
}
@Override public Call call() {
return call;
}
public EventListener eventListener() {
return eventListener;
}
@Override public Request request() {
return request;
}
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
}
4.通过下列代码发现,OkHttp底层有五大基础Interceptor,且位置固定
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
5.五大拦截器 RetryAndFollowUpInterceptor主要处理异常及重试,BridgeInterceptor负责把应用层参数转化为网络层参数,请求应答后又把网络层参数转化为应用层参数,CacheInterceptor复杂缓存处理,ConnectInterceptor负责从连接池中获取连接,使用完成后释放,CallServerInterceptor则负责底层socket传输。
与连接池相关的是RetryAndFollowUpInterceptor和ConnectInterceptor。RetryAndFollowUpInterceptor会生成StreamAllocation,这个类与连接关系紧密。
我们还是重点来看ConnectInterceptor.
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, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
6.重点来看streamAllocation.newStream
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 {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
我们可以沿着findHealthyConnection一步一步往里面跟踪OkHttp是怎么从连接池是请求连接的。那请求到的连接是什么时候释放的呢?RetryAndFollowUpInterceptor这个拦截器通过StreamAllocation来处理的。
7.streamAllocation.findConnection
Internal.instance.get(connectionPool, address, this, null);重点看这里。这个instance隐藏在OkHttpClient类里(赋值)。
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");
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.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// 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;
}
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;
}
}
}
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.
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.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
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.
result.connect(
connectTimeout, readTimeout, writeTimeout, 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;
}
8.最后我们来看一下ConnectionPool
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 */,
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) {
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) {
}
}
}
}
}
};
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
/**
* Create a new connection pool with tuning parameters appropriate for a single-user application.
* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
*/
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
/** Returns the number of idle connections in the pool. */
public synchronized int idleConnectionCount() {
int total = 0;
for (RealConnection connection : connections) {
if (connection.allocations.isEmpty()) total++;
}
return total;
}
/**
* Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included
* only idle connections and HTTP/2 connections. Since OkHttp 2.7 this includes all connections,
* both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently
* in use.
*/
public synchronized int connectionCount() {
return connections.size();
}
/**
* 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.
*/
@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;
}
/**
* 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;
}
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
/**
* Notify this pool that {@code connection} has become idle. Returns true if the connection has
* been removed from the pool and should be closed.
*/
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;
}
}
/** Close and remove all idle connections in the pool. */
public void evictAll() {
List<RealConnection> evictedConnections = new ArrayList<>();
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (connection.allocations.isEmpty()) {
connection.noNewStreams = true;
evictedConnections.add(connection);
i.remove();
}
}
}
for (RealConnection connection : evictedConnections) {
closeQuietly(connection.socket());
}
}
/**
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
* either it has exceeded the keep alive limit or the idle connections limit.
*
* <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
* -1 if no further cleanups are required.
*/
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++;
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;
}
}
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
/**
* Prunes any leaked allocations and then returns the number of remaining live allocations on
* {@code connection}. Allocations are leaked if the connection is tracking them but the
* application code has abandoned them. Leak detection is imprecise and relies on garbage
* collection.
*/
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;
}
// 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;
}
}
return references.size();
}
}
ConnectionPool里使用Deque来存放所有的连接,与mongodb的连接池不同,这个 Deque并没有限制连接的个数,当连接池中无可用连接时便创建新链接,并投入池中。同时cleanupRunnable任务及时清除不满足要求的连接。
几个重要的日志点:
InternalStreamConnection LOGGER.info(format("Opened connection [%s] to %s", getId(), serverId.getAddress()));
DefaultConnectionPool LOGGER.info(format("Closed connection [%s] to %s because %s.", getId(connection), serverId.getAddress(),getReasonForClosing(connection)));