OkHttp解析(二)网络连接

OkHttp解析系列

OkHttp解析(一)从用法看清原理
OkHttp解析(二)网络连接
OkHttp解析(三)关于Okio

第一篇文章中从OkHttp的使用出发讲解了大部分源码思路,而OkHttp关于网络请求的访问则是在Interceptor拦截器中进行的

Socket管理(StreamAllocation)

对于StreamAllocation,第一篇文章中已经介绍过,也讲解了一点,但未详细。我们知道它是在 RetryAndFollowUpInterceptor这个拦截器中创建的,并以此通过责任链传递给下一个拦截器
而它的使用则是在ConnectionInterceptor拦截器中去与服务器建立连接。

更详细的说:建立TCP连接,处理SSL/TLS握手,完成HTTP2协商等过程ConnectInterceptor 中完成,具体是在StreamAllocation.newStream()

向网络写数据,是在CallServerInterceptor中。

要知道OkHttp底层并未使用HttpURLConnection进行网络请求,而是使用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相当于流分配器,用于协调连接、流和请求三者之间的关系。
对于它的成员变量

  • Address:描述某一个特定的服务器地址。

  • Route:表示连接的线路

  • ConnectionPool:连接池,所有连接的请求都保存在这里,内部由线程池维护

  • RouteSelector:线路选择器,用来选择线路和自动重连

  • RealConnection:用来连接到Socket链路

  • HttpStream:则是Http流,它是一个接口,实现类是Http1xStream、Http2xStream。分别对应HTTP/1.1、HTTP/2和SPDY协议

前面说了,StreamAllocation是在RetryAndFollowUpInterceptor创建,看下构造方法

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

这个连接池则是OkHttpClient在Builder里面默认创建的,而Address则是在RetryAndFollowUpInterceptor中根据对应的URL创建出来
此外,这里还创建了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则是默认的路线数据库

  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则会创建一些默认的代理,默认值为Proxy.NO_PROXY,接着复位nextProxyIndex = 0准备进行代理服务器的连接。

在创建StreamAllocation中,主要做了

  • 根据对应了URL创建了指定的服务器地址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);
  }

这里首先会进行网络的请求判断,然后调用到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方法中,则根据设置的超时时间来创建相应了连接RealConnection,接着创建一个与RealConnection绑定的HttpStream,可能是Http2xStream也可能是Http1xStream

可以说真正连接访问的是RealConnection,前面在RouteSelector中,我们已经确定好了对应的端口,地址,接下来就可以进行TCP连接了。我们看下RealConnection的创建获取情况

 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;
      }
    }
    //根据指定路线创建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会创建RealConnection对象

  • 创建出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则是协议,如果是一开始连接,则protocol == null,直到连接成功获取到对应的协议。
先判断当前线路是否有设置代理隧道,如有则调用buildTunneledConnection没有则调用`buildConnection

 private void buildTunneledConnection(int connectTimeout, int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    //创建默认的隧道代理请求
    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进行创建Socket并建立套接字连接(完成三次握手),使得okio库与远程socket建立了I/O连接
接着调用createTunnel创建代理隧道,在这里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方法中创建了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的创建则与Okio库建立了连接。

获取到输入输出流后,就可以在CallServerInterceptor写入请求数据了。

流程图总结前面分析:

在这里插入图片描述

网络连接图

转载:https://www.jianshu.com/p/da071ad21b6d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值