okhttp之ConnectInterceptor

ConnectInterceptor是okhttp中負責和服務端建立連接的一個攔截器,ConnectInterceptor類中代碼看起來是不多,但千萬不要被它純潔的外表給欺騙了,實際上負責的操作都被封裝到其他的類裏了。下面來看代碼:

 @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //獲取StreamAllocation對象,這是在之前的攔截器中就創建好的,這裏只是獲取一遍
    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對象,這是一個用來封裝request和response操作的對象
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //獲取到鏈接
    RealConnection connection = streamAllocation.connection();
    //交給下個攔截器執行
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

上面的代碼就是ConnectInterceptor類中的全部代碼了,主要就是獲取了幾個對象,然後交給組織處理了。其中StreamAllocation是在之前的攔截器中就已經創建好的,這裏主要看一下HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);這句代碼應該是這個攔截器的核心,通過調用newStream(),獲取了一條connection連接。

public HttpCodec newStream(
  OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
。。。。。

    try {
    //找到一個健康的連接
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        //用連接生成HttpCodec
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
}

newStream方法中主要的任務就是找到一條可用的連接,然後生成HttpCodec。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
  int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
  boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
      //1、找到一條連接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
      //  2、判斷連接是否是新生成的,如果  successCount==0 表示新生成的,連接可用 直接返回    
        if (candidate.successCount == 0) {
          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.
      //   3、連接不是新生成的,則檢查連接是否健康  
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        //4、如果不健康 禁止創建新的stream,繼續下一次循環
        noNewStreams();
        continue;
      }

      return candidate;
    }
}

findHealthyConnection中執行了一個while循環來獲取connection,在循環裏一共執行了四步,註釋中已經說明。這裏面有幾個方法需要來看一下,findConnection()、candidate.isHealthy()和noNewStreams()。其中findConnection()放在最後說,先來看另外兩個方法。

 public boolean isHealthy(boolean doExtensiveChecks) {
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
      return false;
    }

    if (http2Connection != null) {
      return !http2Connection.isShutdown();
    }

    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;
}

isHealthy的判斷主要是通過對socket進行判斷,判斷socket是否關閉、讀寫是否正常,如果都正常,說明是可用的,否則不可用。另外對http2也進行了判斷,通過判斷http2Connection是否關閉來判斷http2是否能用。

接下來看如果不可用的情況下,noNewStreams()做了什麼操作。

public void noNewStreams() {
    Socket socket;
    Connection releasedConnection;
    synchronized (connectionPool) {
      releasedConnection = connection;
      socket = deallocate(true, false, false);
      if (connection != null) releasedConnection = null;
    }
    //關閉socket
    closeQuietly(socket);
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
 }

private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        connection.noNewStreams = true;
      }
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        connection = null;
      }
    }
    return socket;
}

可以看出noNewStreams主要調用了dea主要llocate()方法,deallocate()中做的操作主要就是釋放資源。

最後來看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;
    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;
        //當前的connection不能創建新的stream,就置空
      toClose = releaseIfNoNewStreams();
        //如果當前connection不爲空可以直接使用
      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;
      }
        //如果當前connection爲空,就去連接池裏面找一條連接
      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);
    }
    //如果當前的connection可用或者從連接池裏面找到了一條可用的connection,那麼直接返回
    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, pingIntervalMillis,
        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;
 }

findConnection()方法就結束了,代碼註釋裏把幾個大的步驟也做了註釋,就不在詳細的講解了,因爲我對裏面的內容也是一知半解,比如路由那塊兒。

到此呢,分析就結束了。也不想做總結了,因爲感覺寫的有點兒亂.....

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章