OKHTTP深入浅出(六)----拦截器(3)ConnectInterceptor

之前分析了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;
  }

整体的流程如下:

  1. 首先,尝试试用已给数据流分配的连接(已分配连接的情况例如重定向的再次请求,说明上次已有连接。)
  2. 如果没有已分配的连接,就尝试从连接池中匹配获取。因为此时没有路由信息,因此匹配条件:Address一致--host、port、代理等一致,并且匹配的连接可以接受新的数据流。
  3. 如果连接池中也没有获取到,就取下一个代理的路由信息(多个Route,就回有多个IP地址),再次尝试从连接池中获取,此时可能因为连接合并匹配到。
  4. 如果第二次也没有获取到,就创建RealConnection实例,进行TCP + TLS握手,与服务器建立连接。
  5. 此时为了确保http2.0连接的多次复用,会第三次从连接池匹配。因为新建立的连接握手是非线程安全的,因此,此时的连接可能已经在连接池中。
  6. 第三次如果匹配到,就是使用已有连接,释放刚刚新建的连接,没有匹配到就把新连接存到连接池并返回。

     

在深入看一下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);
  }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值