OkHttp源碼分析之基本框架2

接上篇。
(主要從網絡拉取響應分析)從sendRequest方法中可以看到

httpStream = connect();
httpStream.setHttpEngine(this);

接下來,我們就看看connect()方法:
HttpEngine#connect

  private HttpStream connect() throws RouteException, RequestException, IOException {
    boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
    return streamAllocation.newStream(client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis(),
        client.retryOnConnectionFailure(), doExtensiveHealthChecks);
  }

繼續跟蹤
StreamAllocation#newStream

public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws RouteException, IOException {
    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

      HttpStream resultStream;
      if (resultConnection.framedConnection != null) {
        resultStream = new Http2xStream(this, resultConnection.framedConnection);
      } else {
        resultConnection.socket().setSoTimeout(readTimeout);
        resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
        resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
        resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
      }

      synchronized (connectionPool) {
        stream = resultStream;
        return resultStream;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

可以看到這裏創建了RealConnection resultConnection 對象。
具體創建的代碼
StreamAllocation#findHealthyConnection

/**
   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
   * until a healthy connection is found.
   */
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException, RouteException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
      if (candidate.isHealthy(doExtensiveHealthChecks)) {
        return candidate;
      }

      connectionFailed(new IOException());
    }
  }

繼續跟蹤
StreamAllocation#findConnection

/**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   */
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException, RouteException {
    Route selectedRoute;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (stream != null) throw new IllegalStateException("stream != null");
      if (canceled) throw new IOException("Canceled");

      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      // Attempt to get a connection from the pool.
      //如果連接池中已經存在連接,就從中取出(get)RealConnection
      RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
      if (pooledConnection != null) {
        this.connection = pooledConnection;
        return pooledConnection;
      }

      selectedRoute = route;
    }

    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
      synchronized (connectionPool) {
        route = selectedRoute;
      }
    }
    RealConnection newConnection = new RealConnection(selectedRoute);
    acquire(newConnection);
//將建立成功的RealConnection放入(put)連接池緩存
    synchronized (connectionPool) {
      Internal.instance.put(connectionPool, newConnection);
      this.connection = newConnection;
      if (canceled) throw new IOException("Canceled");
    }
//根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
    newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
        connectionRetryEnabled);
    routeDatabase().connected(newConnection.route());

    return newConnection;
  }

到這裏我們先介紹Socket管理(StreamAllocation)。

StreamAllocation

進行HTTP連接需要進行Socket握手,Socket握手的前提是根據域名或代理確定Socket的ip與端口。這個環節主要講了http的握手過程與連接池的管理,分析的對象主要是StreamAllocation

選擇路線與自動重連(RouteSelector)

此步驟用於獲取socket的ip與端口,各位請欣賞源碼中next()的迷之縮進與遞歸,代碼進行了如下事情:
如果Proxy爲null:

  1. 在構造函數中設置代理爲Proxy.NO_PROXY
  2. 如果緩存中的lastInetSocketAddress爲空,就通過DNS(默認是Dns.SYSTEM,包裝了jdk自帶的lookup函數)查詢,並保存結果,注意結果是數組,即一個域名有多個IP,這就是自動重連的來源
  3. 如果還沒有查詢到就遞歸調用next查詢,直到查到爲止
  4. 一切next都沒有枚舉到,拋出NoSuchElementException,退出(這個幾乎見不到)

如果Proxy爲HTTP:

  1. 設置socket的ip爲代理地址的ip
  2. 設置socket的端口爲代理地址的端口
  3. 一切next都沒有枚舉到,拋出NoSuchElementException,退出

連接socket鏈路(RealConnection)

當地址,端口準備好了,就可以進行TCP連接了(也就是我們常說的TCP三次握手),步驟如下:

  1. 如果連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命中就進入下一步
  2. 根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
  3. 將建立成功的RealConnection放入(put)連接池緩存
  4. 如果存在TLS,就根據SSL版本與證書進行安全握手
  5. 構造HttpStream並維護剛剛的socket連接,管道建立完成

釋放socket鏈路(release)

如果不再需要(比如通信完成,連接失敗等)此鏈路後,釋放連接(也就是TCP斷開的握手)

  1. 嘗試從緩存的連接池中刪除(remove)
  2. 如果沒有命中緩存,就直接調用jdk的socket關閉

經過上述分析,相信大家都有了一定的概念,對上面的那段源碼也就看的很自然。如果連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命就根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手。
RealConnection#connect

  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
    Proxy proxy = route.proxy();
    Address address = route.address();

    if (route.address().sslSocketFactory() == null
        && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
      throw new RouteException(new UnknownServiceException(
          "CLEARTEXT communication not supported: " + connectionSpecs));
    }

    while (protocol == null) {
      try {
        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()
            : new Socket(proxy);
        connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
      } catch (IOException e) {
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }
  }

如果存在TLS,就根據SSL版本與證書進行安全握手
RealConnection#connectSocket

  /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
  private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    rawSocket.setSoTimeout(readTimeout);
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      throw new ConnectException("Failed to connect to " + route.socketAddress());
    }
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));

    if (route.address().sslSocketFactory() != null) {
      connectTls(readTimeout, writeTimeout, connectionSpecSelector);
    } else {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
    }

    if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.

      FramedConnection framedConnection = new FramedConnection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .protocol(protocol)
          .listener(this)
          .build();
      framedConnection.sendConnectionPreface();

      // Only assign the framed connection once the preface has been sent successfully.
      this.allocationLimit = framedConnection.maxConcurrentStreams();
      this.framedConnection = framedConnection;
    } else {
      this.allocationLimit = 1;
    }
  }

上面這段代碼寫的就是根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手。

這裏又引入了一概念。HTTP請求序列化/反序列化
下面我們就來分析分析。

HTTP請求序列化/反序列化

分析的對象是HttpStream接口,在HTTP/1.1下是Http1xStream實現的。

獲得HTTP流(httpStream)

以下爲無緩存,無多次302跳轉,網絡良好,HTTP/1.1下的GET訪問實例分析。
我們已經在上文的RealConnection通過connectSocket()構造HttpStream對象並建立套接字連接(完成三次握手)
在connect()有非常重要的一步,它通過okio庫與遠程socket建立了I/O連接,爲了更好的理解,我們可以把它看成管道

//source 用於獲取response
source = Okio.buffer(Okio.source(rawSocket));
//sink 用於write buffer 到server
sink = Okio.buffer(Okio.sink(rawSocket));

拼裝Raw請求與Headers(writeRequestHeaders)

我們通過Request.Builder構建了簡陋的請求後,可能需要進行一些修飾,這時需要使用Interceptors對Request進行進一步的拼裝了。
攔截器是okhttp中強大的流程裝置,它可以用來監控log,修改請求,修改結果,甚至是對用戶透明的GZIP壓縮。類似於函數式編程中的flatmap操作。在okhttp中,內部維護了一個Interceptors的List,通過InterceptorChain進行多次攔截修改操作。
這裏寫圖片描述
源代碼中是自增遞歸(recursive)調用Chain.process(),直到interceptors().size()中的攔截器全部調用完。主要做了兩件事:

  1. 遞歸調用Interceptors,依次入棧對response進行處理
  2. 當全部遞歸出棧完成後,移交給網絡模塊(getResponse)
 if (index < client.interceptors().size()) {

    Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
    Interceptor interceptor = client.interceptors().get(index);
    //遞歸調用Chain.process()
    Response interceptedResponse = interceptor.intercept(chain);
    if (interceptedResponse == null) {
      throw new NullPointerException("application interceptor " + interceptor
          + " returned null");
    }
    return interceptedResponse;
  }
  // No more interceptors. Do HTTP.
  return getResponse(request, forWebSocket);
}

接下來是正式的網絡請求getResponse(),此步驟通過http協議規範將對象中的數據信息序列化爲Raw文本:

  1. 在okhttp中,通過RequestLine,Requst,HttpEngine,Header等參數進行序列化操作,也就是拼裝參數爲socketRaw數據。拼裝方法也比較暴力,直接按照RFC協議要求的格式進行concat輸出就實現了
  2. 通過sink寫入write到socket連接。

獲得響應(readResponseHeaders/Body)

此步驟根據獲取到的Socket純文本,解析爲Response對象,我們可以看成是一個反序列化(通過http協議將Raw文本轉成對象)的過程:
攔截器的設計:

  1. 自定義網絡攔截器請求進行遞歸入棧
  2. 在自定義網絡攔截器的intercept中,調用NetworkInterceptorChain的proceed(request),進行真正的網絡請求(readNetworkResponse)
  3. 接自定義請求遞歸出棧

網絡讀取(readNetworkResponse)分析:

  1. 讀取Raw的第一行,並反序列化爲StatusLine對象
  2. 以Transfer-Encoding: chunked的模式傳輸並組裝Body

接下來進行釋放socket連接,上文已經介紹過了。現在我們就獲得到response對象,可以進行進一步的Gson等操作了。

到目前,基本框架也算是介紹完了。還是那句話,共同學習與進步。

發佈了68 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章