OkHttp源碼解析(十) OKHTTP中連接與請求及總結(jié)

終于到了講解OkHttp中的連接與請求了,這部分內(nèi)容主要是在ConnectInterceptor與CallServerInterceptor中,所以本片文章主要分2部分

  • 1、ConnectInterceptor
  • 2、CallServerInterceptor
  • 3、總結(jié)

一、ConnectInterceptor

顧名思義連接攔截器,這才是真行的開始向服務器發(fā)起器連接。
看下這個類的代碼

/** Opens a connection to the target server and proceeds to the next interceptor. */
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();
    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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

主要看下ConnectInterceptor()方法,里面代碼已經(jīng)很簡單了,受限了通過streamAllocation的newStream方法獲取一個流(HttpCodec 是個接口,根據(jù)協(xié)議的不同,由具體的子類的去實現(xiàn)),第二步就是獲取對應的RealConnection,由于在上一篇文章已經(jīng)詳細解釋了RealConnection和streamAllocation類了,這里就不詳細說了是大概聊一下

StreamAllocation的newStream()內(nèi)部其實是通過findHealthyConnection()方法獲取一個RealConnection,而在findHealthyConnection()里面通過一個while(true)死循環(huán)不斷去調(diào)用findConnection()方法去找RealConnection.而在findConnection()里面其實是真正的尋找RealConnection,而上面提到的findHealthyConnection()里面主要就是調(diào)用findConnection()然后去驗證是否是"健康"的。在findConnection()里面主要是通過3重判斷:1如果有已知連接且可用,則直接返回,2如果在連接池有對應address的連接,則返回,3切換路由再在連接池里面找下,如果有則返回,如果上述三個條件都沒有滿足,則直接new一個RealConnection。然后開始握手,握手結(jié)束后,把連接加入連接池,如果在連接池有重復連接,和合并連接。
至此findHealthyConnection()就分析完畢,給大家看下大縮減后的代碼,如果大家想詳細了解,請看上一篇文章。

  //StreamAllocation.java
  public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    // 省略代碼 
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, this);
    // 省略代碼 
  }

  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);

      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }
      return candidate;
    }
  }

  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
      //省略部分代碼
      //條件1如果有已知連接且可用,則直接返回
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      //條件2 如果在連接池有對應address的連接,則返回
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }

    // 條件3切換路由再在連接池里面找下,如果有則返回
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }

    RealConnection result;
    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) return connection;

      
      route = selectedRoute;
      refusedStreamCount = 0;
      //以上條件都不滿足則new一個
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result);
    }

    // 開始握手
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    //計入數(shù)據(jù)庫
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      //加入連接池
      Internal.instance.put(connectionPool, result);

      // 如果是多路復用,則合并
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);
    return result;
  }

這里再簡單的說下RealConnection的connect()因為這個方法也很重要。不過大家要注意RealConnection的connect()是StreamAllocation調(diào)用的。在RealConnection的connect()的方法里面也是一個while(true)的循環(huán),里面判斷是隧道連接還是普通連接,如果是隧道連接就走connectTunnel(),如果是普通連接則走connectSocket(),最后建立協(xié)議。隧道連接這里就不介紹了,如果大家有興趣就去上一篇文章去看。connectSocket()方噶里面就是通過okio獲取source與sink。establishProtocol()方法建立連接咱們說下,里面判斷是是HTTP/1.1還是HTTP/2.0。如果是HTTP/2.0則通過Builder來創(chuàng)建一個Http2Connection對象,并且調(diào)用Http2Connection對象的start()方法。所以判斷一個RealConnection是否是HTTP/2.0其實很簡單,判斷RealConnection對象的http2Connection屬性是否為null即可,因為只有HTTP/2的時候http2Connection才會被賦值。
代碼如下:

public void connect(
      int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
   //省略部分代碼
    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {
          connectSocket(connectTimeout, readTimeout);
        }
        establishProtocol(connectionSpecSelector);
        break;
      } catch (IOException e) {
          //省略部分代碼
      }
    }

    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }

  private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    //省略部分代碼    
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
  }

 private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }

    connectTls(connectionSpecSelector);

    if (protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .build();
      http2Connection.start();
    }
  }

這時候我們在回來看下findConnection()方法里面的一行代碼

acquire(result)

調(diào)用的是acquire()方法

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

    this.connection = connection;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

代碼簡單,這里解釋一下,每一個RealConnection對象都有一個字段即allocations

public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();

connections中維護了一張在一個連接上的流的鏈表。該鏈表保存的是StreamAllocation的引用。如果connections字段為空,則說明該連接可以被回收,如果不為空,說明被引用,不能被回收。所以O(shè)kHttp使用了類似計數(shù)法與標記擦出法的混合使用。當連接空閑或者釋放的時候,StreamAllcocation的數(shù)量就會漸漸變成0。從而被線程池檢測并回收。

至此StreamAllocation的findHealthyConnection()就分析完畢了。那我們來看下

//StreamAllocation.java
HttpCodec resultCodec = resultConnection.newCodec(client, this);

其實是調(diào)用RealConnection的newCodec()方法

  public HttpCodec newCodec(
      OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(client.readTimeoutMillis());
      source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

上面主要分了HTTP/2和HTTP/1.x,如果是HTTP/2(http2Connection不為null)則構(gòu)建Http2Codec。如果是HTTP/1.x。則構(gòu)建Http1Codec,大家注意一下在構(gòu)建Http2Codec的時候并沒有傳入source和sink。這是為什么那?大家好好想一下,如果大家不知道為什么可以去看一下我前面的一篇介紹HTTP/2的文章,如果看了還不懂,請在下面留言,我給大家解釋下。

至此關(guān)于ConnectInterceptor已經(jīng)介紹完畢了。下面我們來介紹下CallServerInterceptor。最后一個Interceptor

二、CallServerInterceptor

上面我們已經(jīng)成功連接到服務器了,那接下來要做什么那?相信你已經(jīng)猜到了, 那就說發(fā)送數(shù)據(jù)了。

在OkHttp里面讀取數(shù)據(jù)主要是通過以下四個步驟來實現(xiàn)的

  • 1 寫入請求頭
  • 2 寫入請求體
  • 3 讀取響應頭
  • 4 讀取響應體

OkHttp的流程是完全獨立的。同樣讀寫數(shù)據(jù)月是交給相關(guān)的類來處理,就是HttpCodec(解碼器)來處理。

  @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();
    
    long sentRequestMillis = System.currentTimeMillis();
    //寫入請求頭
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }
     //寫入請求體
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
        // being reused. Otherwise we're still obligated to transmit the request body to leave the
        // connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();
    //讀取響應頭
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
    //讀取響應體
    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;
  }

自此整個流程已經(jīng)結(jié)束了。

那我們再來看下OkHttp網(wǎng)絡請求的整體接口圖(特別聲明:這個圖不是我畫的)

okhttp整體架構(gòu).png

關(guān)于OkHttp就的解析馬上就要結(jié)束了,最后我們再來溫習一下整體的流程圖


流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容