OKhttp的理解-RealConnectionPool
RealConnectionPool
构造方案的参数:
maxIdleConnections: 最大连接数,默认为5。
keepAliveDuration: 存活时间,默认为5分钟。
关键方法:
connectionBecameIdle—— 通知RealConnectionPool该connection已经空闲。返回true表示该connection已经从队列中移除,对应的socket可以关闭了。
public final class RealConnectionPool {
public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections; //默认为5
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); //默认为5分钟
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
/**
* 从连接池中获取一个可用的RealConnection,然后把指定的transmitter绑定到这个链接上,并返回true;
* 如果没有找到可用的RealConnection,则返回false.
*/
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
/**
* 通知RealConnectionPool该connection已经空闲。返回true表示该connection已经从队列中移除,对应的socket可以关闭了。
* @param connection
* @return
*/
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
if (connection.noNewExchanges || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
return false;
}
}
}
清理逻辑
采用一个线程池,核心线程数为0、最大线程数为Inter.MAX_VALUE, 空闲线程存活时间为60秒,等待队列为无缓冲队列(SynchronousQueue),即同一时间只能有一个任务在执行。清除流程为:
遍历connections队列,找到空闲最长时间的连接,如果最长空闲时间超过5分钟、或者空闲连接数量大于5,则从队列中删除该连接并关闭socket。
public final class RealConnectionPool {
...
/**
* @param now: 当前时间戳
* @return: 分几种情况:
* > 0:距离下次清除的时间间隔;
* = 0: 已成功清除了最长空闲时间的连接;
* -1: connections队列为空,无空闲或正在使用的连接。
*/
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;
}
/**
* 从连接池中获取一个可用的RealConnection,然后把指定的transmitter绑定到这个链接上,并返回true;
* 如果没有找到可用的RealConnection,则返回false.
*/
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter, @Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
}
Transmitter
1. 初始化
在调用newRealCall()方法时会new一个RealCall实例,创建成功后会再使用该call作为参数new一个Transmitter实例,并赋值给call的transmitter成员变量。
一个请求对应一个Transmitter实例,Transmitter需要一个OkHttpClient和Call实例作为构造函数的参数。
final class RealCall implements Call {
/**
* There is a cycle between the {@link Call} and {@link Transmitter} that makes this awkward.
* This is set after immediately after creating the call instance.
*/
private Transmitter transmitter;
...
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.transmitter = new Transmitter(client, call);
return call;
}
}
2. 关键方法
(1)acquireConnectionNoEvents(RealConnection connection)
每个Transmitter只会调用一次acquireConnectionNoEvents(),它会去绑定一个RealConnection,然后把自己的WeakRefrence<Transmitter>添加到该Connection的transmitter列表中。
(2)releaseConnectionNoEvents()
一个RealConnection维护了transmitters弱引用的列表。
public final class Transmitter {
public RealConnection connection;
public void prepareToConnect(Request request) {
if (this.request != null) {
if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
return; // Already ready.
}
if (exchange != null) throw new IllegalStateException();
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
this.request = request;
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
void acquireConnectionNoEvents(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
...
//将自己从RealConnection的transmitters列表中移除。如果该RealConnection已经没有人在使用了,就返回该Connection的socket,否则表示还有其他请求再用,返回null。
@Nullable Socket releaseConnectionNoEvents() {
assert (Thread.holdsLock(connectionPool));
int index = -1;
for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
Reference<Transmitter> reference = this.connection.transmitters.get(i);
if (reference.get() == this) {
index = i;
break;
}
}
if (index == -1) throw new IllegalStateException();
RealConnection released = this.connection;
released.transmitters.remove(index);
this.connection = null;
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
if (connectionPool.connectionBecameIdle(released)) {
return released.socket();
}
}
return null;
}
}
ExchangeFinder
final class ExchangeFinder {
public ExchangeCodec find(
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();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//返回一个新的Http1ExchangeCodec或者Http2ExchangeCodec.
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
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);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
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;
}
}
/**
* 查找一个可用的RealConnection,这是连接复用的核心方法,查找过程为:
* 1. 先判断transmitter是否已绑定过一个RealConnection、且可用,如果是则直接返回;
* 2. 如果transmitter已绑定过一个RealConnection,但是不可用,则调用transmitter.releaseConnectionNoEvents()释放之;
* 3. 从连接池中获取一个可用的RealConnection(ConnectionPool#transmitterAcquirePooledConnection方法),如果获取失败,则继续步骤4:
* 4. 是否需要一个route selection, 如果需要,则再次尝试从连接池中获取一个可用的RealConnection,如果获取失败,则继续步骤5;
* 5. 新建一个RealConnection,并调用RealConnection#connect方法进行连接,然后再次尝试从连接池中获取一个可用RealConnection, 如果获取失败,则继续步骤6;
* 6. 将新建的RealConnection放在connections队列中,并将transmitter添加到该RealConnection的弱引用队列中(List<Reference<Transmitter>>).
* @param connectTimeout
* @param readTimeout
* @param writeTimeout
* @param pingIntervalMillis
* @param connectionRetryEnabled
* @return
* @throws IOException
*/
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.
//如果该transmitter已经绑定过一个RealConnection、且noNewExchanges为true, 则将该transmitter与RealConnection解绑。
//解绑之后,transmitter.connection等于null.
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
selectedRoute = transmitter.connection.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();
}
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.
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.
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.
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;
// It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
// that case we will retry the route we just successfully connected with.
nextRouteToTry = selectedRoute;
} else {
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
}
ConnectInterceptor
ConnectInterceptor的intercept()方法会调用Transmitter的newExchange()方法。
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();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
}
RealConnection
关键方法:
connect —— 执行socket连接,如果是https协议,需要进行TLS握手。
connectTls方法 —— 进行TLS握手。
isEligible—— 判断该RealConnection是否可以复用。
isHealthy —— 判断RealConnection当前的socket是否是健康的。
public final class RealConnection extends Http2Connection.Listener implements Connection {
...
/**
* 执行连接动作
* @param connectTimeout 连接超时时间
* @param readTimeout 用来设置setSoTimeout
* @param writeTimeout 用于设置connectTunnel中sink的timeout
* @param pingIntervalMillis 用于Http2Connection中以固定频率执行PingRunnable
* @param connectionRetryEnabled 是否允许重试
* @param call
* @param eventListener 事件监听回调
*/
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
...
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
}
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// Configure the socket's ciphers, TLS versions, and extensions.
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
// Force handshake. This can throw!
sslSocket.startHandshake();
// block for session establishment
SSLSession sslSocketSession = sslSocket.getSession();
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
if (!peerCertificates.isEmpty()) {
X509Certificate cert = (X509Certificate) peerCertificates.get(0);
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
} else {
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified (no certificates)");
}
}
// Check that the certificate pinner is satisfied by the certificates presented.
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}
/**
* 判断当前RealConnection是否可以被复用,判断步骤为:
* 第一部分:Host匹配
* 1. 如果该RealConnection上绑定的transmitters已经到达上限,则返回false;
* 2. 如果noNewExchanges为true,则返回false;
* 3. 进行非Host域的比较,如果route中address与参数address进行equalsNonHost比较返回false,则返回false;
* 4. 进行Host比较,如果route中address的host与参数address中的相同,则返回true;
* 第二部分:Host不匹配时,如果是Http/2,还有可能复用现有连接,继续判断:
* 5. 如果http2Connection为null,则返回false;
* 6. 如果routes为null、或者routes中没有能与RealConnection的route匹配的,则返回false;
* 7. 如果address的hostnameVerifier与OkHostnameVerifier不相等,则返回false;
* 8. 如果address的HttpUrl的host和port与route中address不相同,则返回false;
* 9. 如果address的CertificatePinner实例校验失败,则返回false;
* 如果以上都校验通过,则返回true。
* @param address
* @param routes
* @return
*/
/**
* Returns true if this connection can carry a stream allocation to {@code address}. If non-null
* {@code route} is the resolved route for a connection.
*/
boolean isEligible(Address address, @Nullable List<Route> routes) {
// If this connection is not accepting new exchanges, we're done.
if (transmitters.size() >= allocationLimit || noNewExchanges) 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;
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().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.
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. This connection's server certificate's must cover the new host.
if (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.
}
/**
* 判断当前的socket是否是健康的。检验标准是:
* 1. socket未关闭
* 2. socket的输入流未关闭
* 3. socket的输出流未关闭
* 4. 如果是http/2的话,http2未关闭
* 5. 如果需要doExtensiveChecks, 则将setSoTimeout设置为1ms,判断是否会出现SocketTimeoutException.
* @param doExtensiveChecks 是否需要进行严格校验
* @return
*/
public boolean isHealthy(boolean doExtensiveChecks) {
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
if (http2Connection != null) {
return http2Connection.isHealthy(System.nanoTime());
}
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;
}
}
浙公网安备 33010602011771号