okhttp之StreamAllocation

轉載請以鏈接形式標明出處:
本文出自:103style的博客


base on 3.12.0


目錄

  • 背景
  • 簡介
  • StreamAllocation 的成員變量
  • StreamAllocation 的構造函數
  • StreamAllocation 的相關方法
  • 小結

背景

HTTP 的版本從最初的 1.0版本,到後續的 1.1版本,再到後續的 google 推出的SPDY,後來再推出 2.0版本,HTTP協議越來越完善。okhttp也是根據2.0和1.1/1.0作爲區分,實現了兩種連接機制.

http2.0解決了老版本(1.1和1.0)最重要兩個問題:

http2.0 使用 多路複用 的技術,多個 stream 可以共用一個 socket 連接。每個 tcp連接都是通過一個 socket 來完成的,socket 對應一個 hostport,如果有多個stream(即多個 Request) 都是連接在一個 hostport上,那麼它們就可以共同使用同一個 socket ,這樣做的好處就是 可以減少TCP的一個三次握手的時間
OKHttp裏面,負責連接的是 RealConnection


簡介

官方註釋是 StreamAllocation是用來協調ConnectionsStreamsCalls這三個實體的。

HTTP通信 執行 網絡請求Call 需要在 連接Connection 上建立一個新的 Stream,我們將 StreamAllocation 稱之 的橋樑,它負責爲一次 請求 尋找 連接 並建立 ,從而完成遠程通信。

然後我們先來看看 StreamAllocation 這個類是在什麼地方初始化的,以及在哪些地方用到?

選中StreamAllocation 的構造方法,在 AndroidStudio 中按 Alt + F7,我們發現是在 RetryAndFollowUpInterceptor這個攔截器intercept(Chain chain)中創建的.
然後往下層攔截器傳遞,直到 ConnectInterceptor 以及 CallServerInterceptor才繼續用到。

public final class RetryAndFollowUpInterceptor implements Interceptor {
    ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    ...
}
public final class ConnectInterceptor implements Interceptor {
  ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}
public final class CallServerInterceptor implements Interceptor {
  ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
        streamAllocation.noNewStreams();
    ...
  }
 ...
}

StreamAllocation 的成員變量

  • 由構造方法中傳入的變量:
    • public final Address address; 地址
    • private final ConnectionPool connectionPool; 連接池
    • public final Call call; 請求對象
    • public final EventListener eventListener; 事件回調
    • private final Object callStackTrace; 日誌
    • private final RouteSelector routeSelector; 路由選擇器
  • private RouteSelector.Selection routeSelection; 選中的路由集合
  • private Route route; 路由
  • private RealConnection connection; HTTP連接
  • private HttpCodec codec; HTTP請求的編碼和響應的解碼

StreamAllocation 的構造函數

RetryAndFollowUpInterceptor 中傳入了 :

public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
                        EventListener eventListener, Object callStackTrace) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
    this.callStackTrace = callStackTrace;
}

StreamAllocation 的相關方法

我們在簡介中介紹 StreamAllocation 在哪裏創建的時候,發現攔截器中主要調用的就是 newStream(...)noNewStreams() 這兩個方法。那我們先來看看這兩個方法吧。

  • newStream(...)
    主要就是獲取一個 可用的連接 和 對應連接協議的編解碼實例,並賦值給變量 connectioncodec.
    public HttpCodec newStream(...) {
      ...
      try {
        //在連接池中找到一個可用的連接  沒有則創建一個
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
            writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        //獲取對應協議的 編解碼類 Http1Codec or Http2Codec
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
        synchronized (connectionPool) {
          codec = resultCodec;
          return resultCodec;
        }
      } catch (IOException e) {
        throw new RouteException(e);
      }
    }
    
  • noNewStreams()
    主要就是設置當前的 connection 不能再創建新的流。
    public void noNewStreams() {
      Socket socket;
      Connection releasedConnection;
      synchronized (connectionPool) {
        releasedConnection = connection;
        socket = deallocate(true, false, false);
        if (connection != null) releasedConnection = null;
      }
      closeQuietly(socket);
      if (releasedConnection != null) {
        eventListener.connectionReleased(call, releasedConnection);
      }
    }
    

繼續看下 newStream(...) 獲取可用連接的流程。

  • findHealthyConnection(...)
    通過findConnection(...)得到一個連接,如果是新創建的連接,則直接返回,否則檢查 連接 是否已經可以開始承載新的流,不行則繼續findConnection(...).

    private RealConnection findHealthyConnection(...) throws IOException {
      while (true) {
        RealConnection candidate = findConnection(...);
        //如果是一個新創建的連接 則直接返回
        synchronized (connectionPool) {
          if (candidate.successCount == 0) {
            return candidate;
          }
        }
        //否則檢查 連接 是否已經可以開始承載新的流
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
          noNewStreams();
          continue;
        }
        return candidate;
      }
    }
    
  • findConnection(...)

    • 1.0 第一個 synchronized (connectionPool) 這一段首先嚐試使用當前的變量connection. 如果當前的connectionnull, 則通過Internal.instance.get(connectionPool, address, this, null);連接池 中獲取對應 address 的連接 賦值給 connection,如果當前的 connection不爲null,則直接返回.
    • 2.0 如果上一步沒有找到可用連接,則看是否有其他可用路由。
    • 3.0 第二個 synchronized (connectionPool) 這一段,3.1如果有其他路由則先去連接池查詢看是否有對應連接, 3.2沒有的話則創建一個新的 RealConnection,並賦值給變量 connection. 3.3在連接池中找到的話則直接返回。
    • 4.0 然後新創建的連接開始握手連接,然後放入連接池,然後返回。
    private RealConnection findConnection(...) throws IOException {
      ...
      //1.0 嘗試使用當前的 connection,或者查找 連接池
      synchronized (connectionPool) {
        ...
        releasedConnection = this.connection;
        if (this.connection != null) {
          result = this.connection;
          releasedConnection = null;
        }
        if (!reportedAcquired) {
          releasedConnection = null;
        }
        if (result == null) {
          Internal.instance.get(connectionPool, address, this, null);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
          } else {
            selectedRoute = route;
          }
        }
      }
      ...
      if (result != null) {
        return result;
      }
    
      //2.0 看是否有其他的路由
      boolean newRouteSelection = false;
      if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
      }
      //3.0 
      synchronized (connectionPool) {
        if (newRouteSelection) {
          //3.1 有其他路由則先去連接池查詢看是否有對應連接
        }
        if (!foundPooledConnection) {
          ...
          //3.2 沒有的話則創建一個新的RealConnection,並賦值給變量 connection
          result = new RealConnection(connectionPool, selectedRoute);
          acquire(result, false);
        }
      }
      // 3.3 在連接池中找到的話則直接返回
      if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
      }
      //4.0 新創建的連接開始握手連接
      result.connect(...);
      synchronized (connectionPool) {
        reportedAcquired = true;
        Internal.instance.put(connectionPool, result);
        ...
      }
      ...
      return result;
    }
    

    流程圖大概如下:
    okhttp之StreamAllocation.findHealthyConnection(...)

  • acquire(...)從連接池找到對應連接 賦值給StreamAllocation

    public void acquire(RealConnection connection, boolean reportedAcquired) {
      assert (Thread.holdsLock(connectionPool));
      if (this.connection != null) throw new IllegalStateException();
      this.connection = connection;
      this.reportedAcquired = reportedAcquired;
      connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
    }
    
  • 獲取成員變量的一些方法.

    public HttpCodec codec() {
      synchronized (connectionPool) {
        return codec;
      }
    }
    public Route route() {
      return route;
    }
    public synchronized RealConnection connection() {
      return connection;
    }
    
  • release() 資源釋放

    public void release() {
      Socket socket;
      Connection releasedConnection;
      synchronized (connectionPool) {
        releasedConnection = connection;
        socket = deallocate(false, true, false);
        if (connection != null) releasedConnection = null;
      }
      closeQuietly(socket);
      if (releasedConnection != null) {
        Internal.instance.timeoutExit(call, null);
        eventListener.connectionReleased(call, releasedConnection);
        eventListener.callEnd(call);
      }
    }
    

小結

  • 介紹了HTTP2HTTP1 的區別,HTTP2使用 多路複用 的技術,減少了同地址的TCP握手時間。

  • 介紹了 StreamAllocation 的主要方法newStream(...),以及其中獲取可用 Connection 的具體邏輯.

  • newStream(...) 返回的 HttpCodec 這個 編解碼的示例後面也會介紹。


參考文章

如果覺得不錯的話,請幫忙點個贊。

以上

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