OkHttpClient源碼分析(五)—— ConnectInterceptor和CallServerInterceptor

上一篇我們介紹了緩存攔截器CacheInterceptor,本篇將介紹剩下的兩個攔截器: ConnectInterceptorCallServerInterceptor

ConnectInterceptor

該攔截器主要是負責建立可用的鏈接,主要作用是打開了與服務器的鏈接,正式開啓了網絡請求。
查看其intercept()方法:

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //從攔截器鏈中獲取StreamAllocation對象
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    
    //創建HttpCodec對象
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    
    //獲取realConnetion
    RealConnection connection = streamAllocation.connection();

    //執行下一個攔截器,返回response
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

可以看到intercept中的處理很簡單,主要有以下幾步操作:

  1. 從攔截器鏈中獲取StreamAllocation對象,在講解第一個攔截器RetryAndFollowUpInterceptor的時候,我們已經初步瞭解了StreamAllocation對象,在RetryAndFollowUpInterceptor中僅僅只是創建了StreamAllocation對象,並沒有進行使用,到了ConnectInterceptor中,StreamAllocation才被真正使用到,該攔截器的主要功能都交給了StreamAllocation處理;

  2. 執行StreamAllocation對象的 newStream() 方法創建HttpCodec,用於處理編碼Request和解碼Response;

  3. 接着通過調用StreamAllocation對象的 connection() 方法獲取到RealConnection對象,這個RealConnection對象是用來進行實際的網絡IO傳輸的。

  4. 調用攔截器鏈的**proceed()**方法,執行下一個攔截器返回response對象。

上面我們已經瞭解了ConnectInterceptor攔截器的intercept()方法的整體流程,主要的邏輯是在StreamAllocation對象中,我們先看下它的 newStream() 方法:

 public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    ...
    try {
      //創建RealConnection對象
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      //創建HttpCodec對象
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
      
      synchronized (connectionPool) {
        codec = resultCodec;
        //返回HttpCodec對象
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

newStream()方法中,主要是創建了RealConnection對象(用於進行實際的網絡IO傳輸)和HttpCodec對象(用於處理編碼Request和解碼Response),並將HttpCodec對象返回。

findHealthyConnection()方法用於創建RealConnection對象:

 private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {//while循環
      //獲取RealConnection對象
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);
    
      //同步代碼塊判斷RealConnection對象的successCount是否爲0
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          //如果爲0則返回
          return candidate;
        }
      }

      //對鏈接池中不健康的鏈接做銷燬處理
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

以上代碼主要做的事情有:

  1. 開啓一個while循環,通過調用findConnection()方法獲取RealConnection對象賦值給candidate;
  2. 如果candidate 的successCount 爲0,直接返回candidate,while循環結束;
  3. 調用candidate的isHealthy()方法,進行“健康檢查”,如果candidate是一個不“健康”的對象,其中不“健康”指的是Socket沒有關閉、或者它的輸入輸出流沒有關閉,則對調用noNewStreams()方法進行銷燬處理,接着繼續循環。

我們看下findConnection()方法做了哪些操作:

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    ...
    RealConnection result = null;
    ...
    synchronized (connectionPool) {
      ...
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
        //如果不爲 null,則複用,賦值給 result
        result = this.connection;
        releasedConnection = null;
      }
      ...
      //如果result爲 null,說明上面找不到可以複用的
      if (result == null) {
        //從連接池中獲取,調用其get()方法
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          //找到對應的 RealConnection對象
          //更改標誌位,賦值給 result
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    
    ...
    if (result != null) {
      //已經找到 RealConnection對象,直接返回
      return result;
    }
    
    ...
     //連接池中找不到,new一個
     result = new RealConnection(connectionPool, selectedRoute);
    ...
    
    ...
    //發起請求
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    ...
    //存進連接池中,調用其put()方法
    Internal.instance.put(connectionPool, result);
    ...
    return result;
  }

以上代碼主要做的事情有:

  1. StreamAllocation的connection如果可以複用則複用;
  2. 如果connection不能複用,則從連接池中獲取RealConnection對象,獲取成功則返回;
  3. 如果連接池裏沒有,則new一個RealConnection對象;
  4. 調用RealConnection的connect()方法發起請求;
  5. 將RealConnection對象存進連接池中,以便下次複用;
  6. 返回RealConnection對象。

ConnectionPool 連接池介紹

剛纔我們說到從連接池中取出RealConnection對象時調用了Internal的get()方法,存進去的時候調用了其put()方法。其中Internal是一個抽象類,裏面定義了一個靜態變量instance:

public abstract class Internal {
    ...
    public static Internal instance;
    ...
}

instance的實例化是在OkHttpClient的靜態代碼塊中:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  ...
  static {
      Internal.instance = new Internal() {
         ...
          @Override public RealConnection get(ConnectionPool pool, Address address,
          StreamAllocation streamAllocation, Route route) {
            return pool.get(address, streamAllocation, route);
         }
         ...
         @Override public void put(ConnectionPool pool, RealConnection connection) {
           pool.put(connection);
         }
      };
  }
  ...
}

這裏我們可以看到實際上 Internal 的 get()方法和put()方法是調用了 ConnectionPool 的get()方法和put()方法,這裏我們簡單看下ConnectionPool的這兩個方法:

private final Deque<RealConnection> connections = new ArrayDeque<>();

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

在get()方法中,通過遍歷connections(用於存放RealConnection的ArrayDeque隊列),調用RealConnection的isEligible()方法判斷其是否可用,如果可用就會調用streamAllocation的acquire()方法,並返回connection。

我們看下調用StreamAllocation的acquire()方法到底做了什麼操作:

public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    //賦值給全局變量
    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    //創建StreamAllocationReference對象並添加到allocations集合中
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }
  1. 先是從連接池中獲取的RealConnection對象賦值給StreamAllocation的成員變量connection;

  2. 創建StreamAllocationReference對象(StreamAllocation對象的弱引用),
    並添加到RealConnection的allocations集合中,到時可以通過allocations集合的大小來判斷網絡連接次數是否超過OkHttp指定的連接次數。

接着我們查看ConnectionPool 的put()方法:

  void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

put()方法在將連接添加到連接池之前,會先執行清理任務,通過判斷cleanupRunning是否在執行,如果當前清理任務沒有執行,則更改cleanupRunning標識,並執行清理任務cleanupRunnable。

我們看下清理任務cleanupRunnable中到底做了哪些操作:

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

可以看到run()方法裏面是一個while死循環,其中調用了cleanup()方法進行清理操作,同時會返回進行下次清理的間隔時間,如果返回的時間間隔爲-1,則會結束循環,如果不是-1,則會調用wait()方法進行等待,等待完成後又會繼續循環執行,具體的清理操作在cleanup()方法中:

long cleanup(long now) {
    //正在使用的連接數
    int inUseConnectionCount = 0;
    //空閒的連接數
    int idleConnectionCount = 0;
    //空閒時間最長的連接
    RealConnection longestIdleConnection = null;
    //最大的空閒時間,初始化爲 Long 的最小值,用於記錄所有空閒連接中空閒最久的時間
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
      //for循環遍歷connections隊列
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        //如果遍歷到的連接正在使用,則跳過,continue繼續遍歷下一個
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }

        //當前連接處於空閒,空閒連接數++
        idleConnectionCount++;

        //計算空閒時間
        long idleDurationNs = now - connection.idleAtNanos;
        //空閒時間如果超過最大空閒時間
        if (idleDurationNs > longestIdleDurationNs) {
          //重新賦值最大空閒時間
          longestIdleDurationNs = idleDurationNs;
          //賦值空閒最久的連接
          longestIdleConnection = connection;
        }
      }

      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        //如果最大空閒時間超過空閒保活時間或空閒連接數超過最大空閒連接數限制
        //則移除該連接
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        //如果存在空閒連接
        //計算出線程清理的時間即(保活時間-最大空閒時間),並返回
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
         //沒有空閒連接,返回keepAliveDurationNs
        return keepAliveDurationNs;
      } else {
        //連接池中沒有連接存在,返回-1
        cleanupRunning = false;
        return -1;
      }
    }

    //關閉空閒時間最長的連接
    closeQuietly(longestIdleConnection.socket());

    return 0;
  }

cleanup()方法通過for循環遍歷connections隊列,記錄最大空閒時間和空閒時間最長的連接;如果存在超過空閒保活時間或空閒連接數超過最大空閒連接數限制的連接,則從connections中移除,最後執行關閉該連接的操作。

主要是通過pruneAndGetAllocationCount()方法判斷連接是否處於空閒狀態:

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

      ...
      
      references.remove(i);
      connection.noNewStreams = true;
      
      ...
      
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }

    return references.size();
  }

該方法通過for循環遍歷RealConnection的allocations集合,如果當前遍歷到的StreamAllocation被使用就遍歷下一個,否則就將其移除,如果移除後列表爲空,則返回0,所以如果方法的返回值爲0則說明當前連接處於空閒狀態,如果返回值大於0則說明連接正在使用。

CallServerInterceptor

接下來講解最後一個攔截器CallServerInterceptor了,查看intercept()方法:

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //相關對象的獲取 
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    ...
    
    //寫入請求頭
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      //判斷是否有請求體
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        //詢問服務器是否願意接收請求體
        httpCodec.flushRequest();//刷新請求
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        //服務器願意接收請求體
        //寫入請求體
        ...
      } else if (!connection.isMultiplexed()) {
        streamAllocation.noNewStreams();
      }
    }

    //結束請求
    httpCodec.finishRequest();

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      //根據服務器返回的數據構建 responseBuilder對象
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //構建 response對象
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    ...
    
    //設置 response的 body
    response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
   
   //如果請求頭中 Connection對應的值爲 close,則關閉連接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }
    
    ...
    
    return response;
  }

以上代碼具體的流程:

  1. 從攔截器鏈中獲取到保存的相關對象;
  2. 調用HttpCodec的writeRequestHeaders()方法寫入請求頭;
  3. 判斷是否需要寫入請求體,先是判斷請求方法,如果滿足,請求頭通過攜帶特殊字段Expect: 100-continue來詢問服務器是否願意接收請求體;
  4. 結束請求;
  5. 根據服務器返回的數據構建response對象;
  6. 關閉連接;
  7. 返回response;

  好了,到這裏OkHttpClient源碼分析就結束了,相信看完本套源碼解析會加深你對OkHttpClient的認識,同時也學到了其巧妙的代碼設計思路,在閱讀源碼的過程中,我們的編碼能力也逐步提升,如果想要寫更加優質的代碼,閱讀源碼是一件很有幫助的事。

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