之前分析了okhttp 的前三个拦截器RetryAndFollowUpInterceptor、ridgeInterceptor、CacheInterceptor。
当然还有okhttp的系列:
OKHTTP深入浅出(一)----基础理论_王胖子总叫我减肥的博客-CSDN博客
OKHTTP深入浅出(二)----基本用法_王胖子总叫我减肥的博客-CSDN博客
OKHTTP深入浅出(三)----源码流程_王胖子总叫我减肥的博客-CSDN博客
OKHTTP深入浅出(四)----拦截器(1)RetryAndFollowUpInterceptor_王胖子总叫我减肥的博客-CSDN博客
OKHTTP深入浅出(五)----拦截器(2)ridgeInterceptor与CacheInterceptor_王胖子总叫我减肥的博客-CSDN博客
这次我们继续看一下,okhttp的连接拦截器ConnectInterceptor,它主要的功能就是建立连接。
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();
// 1
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 2
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
// 3
return realChain.proceed(request, transmitter, exchange);
}
}
注释1,通过Transmitter transmitter = realChain.transmitter();获取了Transmitter对象。
注释2,使用transmitter对象创建了Exchange实例。
注释3,Exchange、Request、Transmitter作为参数调用拦截器链的process方法。
1、Transmitter
注释1, realChain.transmitter()构建出了一个Transmitter对象,看一下它的构造方法
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
在其构造函数中,传入了OKhttpclient对象、连接池、call、事件监听器、call超时时间。
2、Exchange
注释2,通过调用Transmitter的newExchange()方法创建了Exchange实例,看一下newExchange()方法。
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
if (exchange != null) {
throw new IllegalStateException("cannot make a new request because the previous response "
+ "is still open: please call response.close()");
}
}
//2.1
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
//2.2
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
上述代码的2.1,2.2注释处,通过使用exchangeFinder的find方法,获取了ExchangeCodec(交换编码器)实例,然后将该实例作为参数传入到new Exchange方法中,创建了Exchange实例,并返回。
先看一下连接池RealConnectionPool、交换查找器ExchangeFinder、交换编码器ExchangeCodec、交换管理Exchange各自的功能。
- RealConnectionPool:连接池,负责管理请求的连接,例如请求的新建、复用、关闭等。
- ExchangeCodec:交换编码器,是一个接口类,负责真正的IO操作---写请求、读响应,实现类有http1ExchangeCodec、http2ExchangeCodec,对应着http1.1和1.2协议。
- Exchange:交换管理器,可以理解为数据流,是ExchangeCodec的包装类,增加了时间的回调;一个请求对应一个Exchange实例,传给下一个拦截器callServerInterceptor使用。
- ExchangeFinder:交换查找器,从连接池中寻找可用的TCP连接,然后通过连接得到Exchangecodec
注释2.1,exchangeFinder是怎么来的呢?查看RetryAndFollowUpInterceptor拦截器,发现在其intercept()方法中调用的transmitter.prepareToConnect(request),创建了exchangeFinder实例。
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);
}
创建ExchangeFinder(交换查找器),是为了获取连接做准备,作用是获取请求的连接。connectionpool是在OKhttpclient.build--->new ConnectionPool --->new RealConnectionPool创建的,CreateAddress方法返回的是Address。
在深入createAddress()看一下
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
在该方法中使用URL和Client的配置,创建了一个Address实例。Address的意思是指向服务的连接的地址,可以理解为请求地址机器配置。Address的重要作用:相同Address的HTTP请求共享相同的连接。
在深入看一下ExchangeFinder的构造函数
ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
Address address, Call call, EventListener eventListener) {
this.transmitter = transmitter;
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
this.routeSelector = new RouteSelector(
address, connectionPool.routeDatabase, call, eventListener);
}
它在传入自身请求的同时也创建 了一个路由选择器RouteSelector。
回到注释2.1,ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks); 上面阐述了exchangeFinder的构造,下面看一下find()方法
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 {
// 2.3
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
// 2.4
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
注释2.3,表示寻找一个健康的连接,我们深入看一下findHealthyConnection()方法
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);
//是新连接,并且不是HTTP2.0 就不用急检查
// 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;
}
}
循环查找连接,如果连接是不健康的,标记不可用,并且将其从连接池中移除。然后继续寻找,健康的连接是指可以承载新的数据流,socket是连接状态。
继续深入看一下findConnection(),该方法为承载新的数据流寻找连接,寻找的顺序是已分配的连接、连接池、新建连接
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) {
// 请求被取消,抛出异常,call的cancel()方法会调用Transmitter的cancel方法
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt.
// 尝试使用已经给数据流分配的连接,例如重定向请求时,可以复用上次的连接
// 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 exchanges.
releasedConnection = transmitter.connection;
// 有已分配的连接,但是已经被限制承载新的数据流,就尝试释放掉(如果连接上没有数据流)
// 并返回待关闭的socket
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
// 如果连接不为空,说明没有被释放,那么次连接可以重用
result = transmitter.connection;
releasedConnection = null;
}
// 上面没有获取到连接
if (result == null) {
// 尝试从连接池中获取可用的连接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
// 如果获取不到,那么就从Route里面获取
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
// 如果当前的路由是重试的路由,那么就从路由里面获取
selectedRoute = transmitter.connection.route();
}
}
}
//(如果有连接)关闭待关闭的socket
closeQuietly(toClose);
if (releasedConnection != null) {
//(如果有连接)回调连接释放事件
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
//(如果有连接)回调(从连接池)获取连接事件
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// 如果获取到连接,那么就直接返回结果
return result;
}
//如果前面没有获取到连接,那么这里就通过RouteSelector
//先获取到Route,然后再获取Connection。是阻塞操作。
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
// 通过RouteSelector获取Route
// routeSelector在RetryAndFollowUpInterceptor#prepareToConnect
// 的new ExchangeFinder()中创建了
// next()获取routeSelection
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// 通过RouteSelector拿到Route集合(IP地址),
//再次尝试从缓存池中获取连接,看看是否有可以复用的连接;
// 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;
}
// 开始TCP握手和TSL握手,这是一个阻塞的过程
// 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;
// 第三次,也是最后一次尝试从连接池获取,注意最后一个参数为true,即要求 多路复用(http2.0)
// 意思是,如果本次是http2.0,那么为了保证 多路复用性,
// (因为上面的握手操作不是线程安全)会再次确认连接池中此时是否已有同样连接
// 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)) {
// 如果获取到,就关闭我们创建里的连接,返回获取的连接
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
// 那么这个刚刚连接成功的路由 就可以 用作下次 尝试的路由
nextRouteToTry = selectedRoute;
} else {
// 将连接成功的RealConnection放到缓存池中,用于后续复用
connectionPool.put(result);
//transmitter和连接关联起来
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket); //如果刚刚建立的连接没用到,就关闭
eventListener.connectionAcquired(call, result);
return result;
}
整体的流程如下:
- 首先,尝试试用已给数据流分配的连接(已分配连接的情况例如重定向的再次请求,说明上次已有连接。)
- 如果没有已分配的连接,就尝试从连接池中匹配获取。因为此时没有路由信息,因此匹配条件:Address一致--host、port、代理等一致,并且匹配的连接可以接受新的数据流。
- 如果连接池中也没有获取到,就取下一个代理的路由信息(多个Route,就回有多个IP地址),再次尝试从连接池中获取,此时可能因为连接合并匹配到。
- 如果第二次也没有获取到,就创建RealConnection实例,进行TCP + TLS握手,与服务器建立连接。
- 此时为了确保http2.0连接的多次复用,会第三次从连接池匹配。因为新建立的连接握手是非线程安全的,因此,此时的连接可能已经在连接池中。
- 第三次如果匹配到,就是使用已有连接,释放刚刚新建的连接,没有匹配到就把新连接存到连接池并返回。
在深入看一下findconnection()方法:
RouteSelector
首先我们先看一下Route类
public final class Route {
final Address address;
final Proxy proxy;
final InetSocketAddress inetSocketAddress;
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) {
if (address == null) {
throw new NullPointerException("address == null");
}
if (proxy == null) {
throw new NullPointerException("proxy == null");
}
if (inetSocketAddress == null) {
throw new NullPointerException("inetSocketAddress == null");
}
this.address = address;
this.proxy = proxy;
this.inetSocketAddress = inetSocketAddress;
}
public Address address() {
return address;
}
.....
}
Route,通过代理服务器信息proxy、连接目标地址inetSocketAddress来构造一个连接服务器的具体路由。
- proxy代理:可以为客户端显式配置代理服务器,否则,将使用ProxySelector代理选择器,可能返回多个代理。
- IP地址:无论是直连还是代理,打开Socket都需要连接IP地址。DNS服务可能返回多个IP地址尝试。
在上面的findConnection()方法通过routes = routeSelection.getAll(); 获取Route集合routes,routeSelection是通过routeSelection = routeSelector.next();得到的,routeSelector是在ExchangeFinder的构造方法内创建的,也就是说routeSelector在RetryAndFollowUpInterceptor中就创建了。
RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
EventListener eventListener) {
this.address = address;
this.routeDatabase = routeDatabase;//注意:这个是连接池中的路由黑名单(连接失败的路由)
this.call = call;
this.eventListener = eventListener;
//收集代理服务器
resetNextProxy(address.url(), address.proxy());
}
private void resetNextProxy(HttpUrl url, Proxy proxy) {
if (proxy != null) {
// 若指定了代理,那么就这一个。(就是初始化OkhttpClient时配置的)
// If the user specifies a proxy, try that and only that.
proxies = Collections.singletonList(proxy);
} else {
// 没配置就使用ProxySelector获取代理
// (若初始化OkhttpClient时没有配置ProxySelector,会使用系统默认的)
// Try each of the ProxySelector choices until one connection succeeds.
List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
? Util.immutableList(proxiesOrNull)
: Util.immutableList(Proxy.NO_PROXY);
}
nextProxyIndex = 0;
}
注意到RouteSelector的构造方法中传入了routeDatabase,是连接失败的路由黑名单(后面连接池也会讲到),并使用resetNextProxy方法获取代理服务器列表:若没有指定proxy就是用ProxySelector获取proxy列表(若没有配置ProxySelector会使用系统默认)。
再看一下next方法:
public Selection next() throws IOException {
if (!hasNext()) {
throw new NoSuchElementException();
}
//还有下一个
// Compute the next set of routes to attempt.
List<Route> routes = new ArrayList<>();
while (hasNextProxy()) {
// Postponed routes are always tried last. For example, if we have 2 proxies and all the
// routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
// all the good routes will we attempt the postponed routes.
//获取下一个代理Proxy的代理去尝试
//nextProxy->resetNextInetSocketAddress->inetSocketAddresses.add
Proxy proxy = nextProxy();
// 遍历Proxy经DNS后的所有IP地址,组装成Route
for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
Route route = new Route(address, proxy, inetSocketAddresses.get(i));
//此路由在黑名单中,存起来最后尝试
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
} else {
//保持起来
routes.add(route);
}
}
if (!routes.isEmpty()) {
break;
}
}
// 若没有拿到路由,就尝试上面存的黑名单的路由
if (routes.isEmpty()) {
// We've exhausted all Proxies so fallback to the postponed routes.
routes.addAll(postponedRoutes);
postponedRoutes.clear();
}
//routes包装成Selection返回
return new Selection(routes);
}
next方法主要就是获取下一个代理Proxy的代理信息,即多个路由。具体是在resetNextInetSocketAddress方法中实现,主要是对代理服务地址进行DNS解析获取多个IP地址。
ConnectionPool
ConnectionPool,即连接池,用于管理http1.1/http2.0连接重用,以减少网络延迟。相同Address的http请求可以共享一个连接,ConnectionPool就是实现了连接的复用。
OkHttpClient初始化时,实例化了ConnectionPool
public final class ConnectionPool {
final RealConnectionPool delegate;
public ConnectionPool() {
//默认的连接池,最大连接5,保持Alive持续时间5min
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
}
//返回空闲连接数
public int idleConnectionCount() {
return delegate.idleConnectionCount();
}
//返回池子中的连接数
public int connectionCount() {
return delegate.connectionCount();
}
//关闭并移除所有空闲连接
public void evictAll() {
delegate.evictAll();
}
}
ConnectionPool默认配置是最大空闲连接数5,最大空闲时间5分钟(即一个连接空闲时间超过5分钟就移除),我们也可以在初始化okhttpClient时进行不同的配置。需要注意的是ConnectionPool是用于应用层,实际管理者是RealConnectionPool。RealConnectionPool是okhttp内部真实管理连接的地方。用到了代理模式。RealConnectionPool实现如下:
public final class RealConnectionPool {
//线程池,用于清理过期的连接。一个连接池最多运行一个线程。
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
//ConnectionPool的构造方法
//每个ip地址的最大空闲连接数,为5个
private final int maxIdleConnections;
//空闲连接存活时间,为5分钟
private final long keepAliveDurationNs;
//连接队列
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
public synchronized int idleConnectionCount() {
int total = 0;
for (RealConnection connection : connections) {
if (connection.transmitters.isEmpty()) total++;
}
return total;
}
public synchronized int connectionCount() {
return connections.size();
}
...
}
add:
private final Runnable cleanupRunnable = () -> {
//循环清理
while (true) {
//移除连接,executor运行cleanupRunnable,调用该方法
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
//下一次清理之前的等待
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
//存连接
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//未正在清理,加之前先清理一下
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
connections是用于存连接的队列Deque。看到在add之前,使用线程池executor执行了cleanupRunnable,意思是清理连接。并且注意到清理是一个循环,并且下一次清理前要等待waitNanos时间。我们看下cleanup方法:
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();
//若连接正在使用,continue,正在使用连接数+1
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
//空闲连接数+1
idleConnectionCount++;
// 赋值最长的空闲时间和对应连接
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}//...for
//若最长的空闲时间大于5分钟 或 空闲数 大于5,就移除并关闭这个连接
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) {
// else 空闲连接数 > 0,就返回 还剩多久到达5分钟,然后wait这个时间再来清理
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 连接正在使用没有空闲,就5分钟后再尝试清理.
// 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;
}
- 有空闲连接的话,如果最长的空闲时间大于5分钟 或 空闲数 大于5,就移除关闭这个最长空闲连接;如果 空闲数 不大于5 且 最长的空闲时间不大于5分钟,就返回到5分钟的剩余时间,然后等待这个时间再来清理。
- 没有空闲连接就等5分钟后再尝试清理。
- 没有连接不清理。
其中判断连接正在使用的方法pruneAndGetAllocationCount我们来看下:
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
//连接上的数据流,弱引用列表
List<Reference<Transmitter>> references = connection.transmitters;
for (int i = 0; i < references.size(); ) {
Reference<Transmitter> reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
// 到这里,transmitter是泄漏的,要移除
// 且此连接不能再承载新的数据流(泄漏的原因就是用户忘记close response body)
// We've discovered a leaked transmitter. This is an application bug.
TransmitterReference transmitterRef = (TransmitterReference) reference;
String message = "A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);
references.remove(i);
connection.noNewExchanges = true;
//连接因为泄漏没有数据流了,那么可以立即移除了。所以设置 开始空闲时间 是5分钟前
// If this was the last allocation, the connection is eligible for immediate eviction.
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//返回连接上的数据流数量,大于0说明正在使用。
return references.size();
}
另外,在findConnection中,使用connectionPool.put(result)存连接后,又调用transmitter.acquireConnectionNoEvents方法:
void acquireConnectionNoEvents(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
//先把连接赋给transmitter,表示数据流transmitter依附在这个connection上
//add 这个transmitter的弱引用
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
先把连接赋给transmitter,表示数据流transmitter依附在这个connection上;然后connection.transmitters add 这个transmitter的弱引用,connection.transmitters表示这个连接承载的所有数据流,即承载的所有请求。
主要就是把连接存入队列,同时开始循环尝试清理过期连接。
PUT :
//获取连接
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分配一个连接
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
存的方法名是put,取的方法名却不是get,transmitterAcquirePooledConnection意思是 为transmitter 从连接池 获取连接,实际上transmitter就代表一个数据流,也就是一个http请求。注意到,在遍历中 经过判断后也是transmitter的acquireConnectionNoEvents方法,即把匹配到的connection赋给transmitter。
继续看是如何匹配的:如果requireMultiplexed为false,即不是多路复用(不是http/2),那么就要看Connection的isEligible方法了,isEligible方法返回true,就代表匹配成功:
//用于判断 连接 是否 可以承载指向address的数据流
boolean isEligible(Address address, @Nullable List<Route> routes) {
// If this connection is not accepting new exchanges, we're done.
// 连接不再接受新的数据流,false
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// If the non-host fields of the address don't overlap, we're done.
// 匹配address中非host的部分
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// If the host exactly matches, we're done: this connection can carry the address.
// 匹配address的host,到这里也匹配的话,就return true
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/
//到这里hostname是没匹配的,但是还是有机会返回true:连接合并
// 1. 连接须是 HTTP/2.
// 1. This connection must be HTTP/2.
if (http2Connection == null) return false;
// 2. IP 地址匹配
// 2. The routes must share an IP address.
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. 证书匹配
// 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. 证书 pinning 匹配.
// 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.
}
取的过程就是遍历连接池,进行地址等一系列匹配。
remove:
//移除关闭空闲连接
public void evictAll() {
List<RealConnection> evictedConnections = new ArrayList<>();
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//如果连接上的transmitters是空,那么就从连接池移除并且关闭。
if (connection.transmitters.isEmpty()) {
connection.noNewExchanges = true;
evictedConnections.add(connection);
i.remove();
}
}
}
for (RealConnection connection : evictedConnections) {
closeQuietly(connection.socket());
}
}
遍历连接池,如果连接上的数据流是空,那么就从连接池移除并且关闭。
我们回过头看下Transmitter的releaseConnectionNoEvents方法,如果连接不再接受新的数据流,就会调用这个方法:
//从连接上移除transmitter
@Nullable Socket releaseConnectionNoEvents() {
assert (Thread.holdsLock(connectionPool));
int index = -1;
//遍历 此数据流依附的连接 上的所有数据流,找到index
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();
//transmitters移除此数据流
RealConnection released = this.connection;
released.transmitters.remove(index);
this.connection = null;
//如果连接上没有有数据流了,就置为空闲(等待清理),并返回待关闭的socket
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
if (connectionPool.connectionBecameIdle(released)) {
return released.socket();
}
}
return null;
}
主要就是尝试释放连接,连接上没有数据流就关闭socket等待被清理
回头看一下注释2.4 ,return resultConnection.newCodec(client, chain), 使用找到的连接Realconnection的newcodec方法,返回ExchangeCodec对象,如果是如果是HTTP/2返回Http2ExchangeCodec,否则返回Http1ExchangeCodec
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
// http2Connection不为空就创建Http2ExchangeCodec,否则是Http1ExchangeCodec
if (http2Connection != null) {
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}