OkHttp解析(二)網(wǎng)絡連接

OkHttp解析系列

OkHttp解析(一)從用法看清原理
OkHttp解析(二)網(wǎng)絡連接
OkHttp解析(三)關于Okio

第一篇文章中從OkHttp的使用出發(fā)講解了大部分源碼思路,而OkHttp關于網(wǎng)絡請求的訪問則是在Interceptor攔截器中進行的

Socket管理(StreamAllocation)

對于StreamAllocation,第一篇文章中已經(jīng)介紹過,也講解了一點,但未詳細。我們知道它是在 RetryAndFollowUpInterceptor這個攔截器中創(chuàng)建的,并以此通過責任鏈傳遞給下一個攔截器
而它的使用則是在ConnectionInterceptor攔截器中去與服務器建立連接。

更詳細的說:建立TCP連接,處理SSL/TLS握手,完成HTTP2協(xié)商等過程ConnectInterceptor 中完成,具體是在StreamAllocation.newStream()

向網(wǎng)絡寫數(shù)據(jù),是在CallServerInterceptor中。

要知道OkHttp底層并未使用HttpURLConnection進行網(wǎng)絡請求,而是使用Socket。

public final class StreamAllocation {
  public final Address address;
  private Route route;
  private final ConnectionPool connectionPool;

  // State guarded by connectionPool.
  private final RouteSelector routeSelector;
  private int refusedStreamCount;
  private RealConnection connection;
  private boolean released;
  private boolean canceled;
  private HttpStream stream;
  ...
}

StreamAllocation相當于流分配器,用于協(xié)調連接、流和請求三者之間的關系。
對于它的成員變量

  • Address:描述某一個特定的服務器地址。

  • Route:表示連接的線路

  • ConnectionPool:連接池,所有連接的請求都保存在這里,內部由線程池維護

  • RouteSelector:線路選擇器,用來選擇線路和自動重連

  • RealConnection:用來連接到Socket鏈路

  • HttpStream:則是Http流,它是一個接口,實現(xiàn)類是Http1xStream、Http2xStream。分別對應HTTP/1.1、HTTP/2和SPDY協(xié)議

前面說了,StreamAllocation是在RetryAndFollowUpInterceptor創(chuàng)建,看下構造方法

  public StreamAllocation(ConnectionPool connectionPool, Address address) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.routeSelector = new RouteSelector(address, routeDatabase());
  }

這個連接池則是OkHttpClient在Builder里面默認創(chuàng)建的,而Address則是在RetryAndFollowUpInterceptor中根據(jù)對應的URL創(chuàng)建出來
此外,這里還創(chuàng)建了RouteSelector

public final class RouteSelector {
/*最近使用的路線,一條線路包括代理和Socket地址 */
private Proxy lastProxy;
private InetSocketAddress lastInetSocketAddress;

/*負責下一個代理的使用 */
private List<Proxy> proxies = Collections.emptyList();
private int nextProxyIndex;

/*負責下一個Socket地址的使用*/
private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
private int nextInetSocketAddressIndex;

/* 失敗的線路集合 */
private final List<Route> postponedRoutes = new ArrayList<>();
...
public RouteSelector(Address address, RouteDatabase routeDatabase) {
    this.address = address;
    this.routeDatabase = routeDatabase;

    resetNextProxy(address.url(), address.proxy());
  }
}

RouteDatabase則是默認的路線數(shù)據(jù)庫

  private void resetNextProxy(HttpUrl url, Proxy proxy) {
    if (proxy != null) {
      // If the user specifies a proxy, try that and only that.
      proxies = Collections.singletonList(proxy);
    } else {
      // Try each of the ProxySelector choices until one connection succeeds. If none succeed
      // then we'll try a direct connection below.
      proxies = new ArrayList<>();
      List<Proxy> selectedProxies = address.proxySelector().select(url.uri());
      if (selectedProxies != null) proxies.addAll(selectedProxies);
      // Finally try a direct connection. We only try it once!
      proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
      proxies.add(Proxy.NO_PROXY);
    }
    nextProxyIndex = 0;
  }

如果用戶指定了代理,也就是proxy != null,則賦值到proxies中,表示確定了下一個代理的位置
如果沒有指定代理,proxy == null則會創(chuàng)建一些默認的代理,默認值為Proxy.NO_PROXY,接著復位nextProxyIndex = 0準備進行代理服務器的連接。

在創(chuàng)建StreamAllocation中,主要做了

  • 根據(jù)對應了URL創(chuàng)建了指定的服務器地址Address

  • 設置了對應的代理,選定下一個代理服務器

接著到了StreamAllocation的使用,對應在ConnectionInterceptor

 @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");
    HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

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

這里首先會進行網(wǎng)絡的請求判斷,然后調用到streamAllocation.newStream接著是streamAllocation.connection

 public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

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

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

streamAllocation.newStream方法中,則根據(jù)設置的超時時間來創(chuàng)建相應了連接RealConnection,接著創(chuàng)建一個與RealConnection綁定的HttpStream,可能是Http2xStream也可能是Http1xStream

可以說真正連接訪問的是RealConnection,前面在RouteSelector中,我們已經(jīng)確定好了對應的端口,地址,接下來就可以進行TCP連接了。我們看下RealConnection的創(chuàng)建獲取情況

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

該方法用來找尋一個合適的連接

 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
      ...
      //默認連接
      RealConnection allocatedConnection = this.connection;
      ...
      //從連接池中獲取RealConnection
      RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
      if (pooledConnection != null) {
        this.connection = pooledConnection;
        return pooledConnection;
      }
      //如果連接池中獲取到,則獲取上次的路線
      selectedRoute = route;
    }
    //如果連接池中沒有,則調用RouteSelector.next獲取對應的線路,該方法會遞歸調用next選擇合適的路線出來
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
      synchronized (connectionPool) {
        route = selectedRoute;
        refusedStreamCount = 0;
      }
    }
    //根據(jù)指定路線創(chuàng)建RealConnection
    RealConnection newConnection = new RealConnection(selectedRoute);
    acquire(newConnection);
    ...
    //連接
    newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
        connectionRetryEnabled);
    routeDatabase().connected(newConnection.route());

    return newConnection;
  }

可以看到,在streamAllocation.newStream調用
RealConnection resultConnection = findHealthyConnection(...);做了幾件事

  • 換取連接池中已有的路線

  • 如果連接池中沒有,則調用RouteSelector.next獲取對應的線路,該方法會遞歸調用next選擇合適的路線出來

  • 選擇出對應合適的路線后Route會創(chuàng)建RealConnection對象

  • 創(chuàng)建出RealConnection對象后調用了realConnection.connected

選擇線路后如何連接

 public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) {
    if (protocol != null) throw new IllegalStateException("already connected");
    ...
    while (protocol == null) {
      try {
        if (route.requiresTunnel()) {
          buildTunneledConnection(connectTimeout, readTimeout, writeTimeout,
              connectionSpecSelector);
        } else {
          buildConnection(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
        }
      }
      ...
    }
  }

在這里protocol則是協(xié)議,如果是一開始連接,則protocol == null,直到連接成功獲取到對應的協(xié)議。
先判斷當前線路是否有設置代理隧道,如有則調用buildTunneledConnection沒有則調用`buildConnection

 private void buildTunneledConnection(int connectTimeout, int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    //創(chuàng)建默認的隧道代理請求
    Request tunnelRequest = createTunnelRequest();
    HttpUrl url = tunnelRequest.url();
    int attemptedConnections = 0;
    int maxAttempts = 21;
    while (true) {
      ...

      connectSocket(connectTimeout, readTimeout);
      tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

      if (tunnelRequest == null) break; // Tunnel successfully created.
      //關閉Socket連接,復位
      closeQuietly(rawSocket);
      rawSocket = null;
      sink = null;
      source = null;
    }

    establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
  }

在這里先調用connectSocket進行創(chuàng)建Socket并建立套接字連接(完成三次握手),使得okio庫與遠程socket建立了I/O連接
接著調用createTunnel創(chuàng)建代理隧道,在這里HttpStream與Okio建立了I/O連接
當連接建立完畢后關閉Socket連接。

connectSocket方法中

  private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

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

可以看到在connectSocket方法中創(chuàng)建了Socket,并且調用socket的連接。
Socket連接后則使用Okio庫與Socket進行I/O連接

 private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,
      HttpUrl url) throws IOException {
    // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
    String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
    while (true) {
      Http1xStream tunnelConnection = new Http1xStream(null, null, source, sink);
      source.timeout().timeout(readTimeout, MILLISECONDS);
      sink.timeout().timeout(writeTimeout, MILLISECONDS);
      tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
      tunnelConnection.finishRequest();
     ...
    }
  }

而在這里HttpStream的創(chuàng)建則與Okio庫建立了連接。

獲取到輸入輸出流后,就可以在CallServerInterceptor寫入請求數(shù)據(jù)了。

流程圖總結前面分析:

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

推薦閱讀更多精彩內容