OKHttp源码解析(九):OKHTTP连接中三个"核心"RealConnection、ConnectionPool、StreamAllocation

本片文章终于讲到OKHTTP中的核心了,复用连接池,本片文章的顺序是

  • 1、RealConnection类
  • 2、ConnectionPool类
  • 3、StreamAllocation类

一、RealConnection

RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过
RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路了。很多朋友这时候会想到,有通信链路了,是不是与意味着在这个类实现的三次握手,你们猜对了,的确是在这个类里面实现的三次握手。在讲握手的之前,看下它的属性和构造函数,对他有个大概的了解。

  private final ConnectionPool connectionPool;
  private final Route route;

  // The fields below are initialized by connect() and never reassigned.
  //下面这些字段,通过connect()方法开始初始化,并且绝对不会再次赋值
  /** The low-level TCP socket. */
  private Socket rawSocket; //底层socket
  /**
   * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
   * {@link #rawSocket} itself if this connection does not use SSL.
   */
  private Socket socket;  //应用层socket
  //握手
  private Handshake handshake;
   //协议
  private Protocol protocol;
   // http2的链接
  private Http2Connection http2Connection;
  //通过source和sink,大家可以猜到是与服务器交互的输入输出流
  private BufferedSource source;
  private BufferedSink sink;

  // The fields below track connection state and are guarded by connectionPool.
  //下面这个字段是 属于表示链接状态的字段,并且有connectPool统一管理
  /** If true, no new streams can be created on this connection. Once true this is always true. */
  //如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
  public boolean noNewStreams;
  
  //成功的次数
  public int successCount;

  /**
   * The maximum number of concurrent streams that can be carried by this connection. If {@code
   * allocations.size() < allocationLimit} then new streams can be created on this connection.
   */
  //此链接可以承载最大并发流的限制,如果不超过限制,可以随意增加
  public int allocationLimit = 1;

通过上面代码,我们可以得出以下结论:

1、里面除了route 字段,部分的字段都是在connect()方法里面赋值的,并且不会再次赋值
2、这里含有source和sink,所以可以以流的形式对服务器进行交互
3、noNewStream可以简单理解为它表示该连接不可用。这个值一旦被设为true,则这个conncetion则不会再创建stream。
4、allocationLimit是分配流的数量上限,一个connection最大只能支持一个1并发
5、allocations是关联StreamAllocation,它用来统计在一个连接上建立了哪些流,通过StreamAllocation的acquire方法和release方法可以将一个allcation对方添加到链表或者移除链表,

其实大家估计已经猜到了connect()里面进行了三次握手,大家也猜对了,那咱们就简单的介绍下connect()方法:

public void connect( int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
    if (protocol != null) throw new IllegalStateException("already connected");
     // 线路的选择
    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    }
    // 连接开始
    while (true) {
      try {
        // 如果要求隧道模式,建立通道连接,通常不是这种
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {
           // 一般都走这条逻辑了,实际上很简单就是socket的连接
          connectSocket(connectTimeout, readTimeout);
        }
        // https的建立
        establishProtocol(connectionSpecSelector);
        break;
      } catch (IOException e) {
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

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

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

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

这里的执行过程大体如下;

  • 1、检查连接是否已经建立,若已经建立,则抛出异常,否则继续,连接的是否简历由protocol标示,它表示在整个连接建立,及可能的协商过程中选择所有要用到的协议。
  • 2、用 集合connnectionspecs构造ConnectionSpecSelector。
  • 3、如果请求是不安全的请求,会对请求执行一些额外的限制:
    3.1、ConnectionSpec集合必须包含ConnectionSpec.CLEARTEXT。也就是说OkHttp用户可以通过OkHttpClient设置不包含ConnectionSpec.CLEARTEXT的ConnectionSpec集合来禁用所有的明文要求。
    3.2、平台本身的安全策略允向相应的主机发送明文请求。对于Android平台而言,这种安全策略主要由系统的组件android.security.NetworkSecurityPolicy执行。平台的这种安全策略不是每个Android版本都有的。Android6.0之后存在这种控制。
    (okhttp/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java 里面的isCleartextTrafficPermitted()方法)
  • 4、根据请求判断是否需要建立隧道连接,如果建立隧道连接则调用
    connectTunnel(connectTimeout, readTimeout, writeTimeout);
  • 5、如果不是隧道连接则调用connectSocket(connectTimeout, readTimeout);建立普通连接。
  • 6、通过调用establishProtocol建立协议
  • 7、如果是HTTP/2,则设置相关属性。

整个流程已经梳理完,咱们就抠一下具体的细节,首先来看下建立普通连接,因为隧道连接也会用到普通连接的代码:
看下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) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();
     // 根据代理类型来选择socket类型,是代理还是直连
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
      // 连接socket,之所以这样写是因为支持不同的平台
      //里面实际上是  socket.connect(address, connectTimeout);
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
     // 得到输入/输出流
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
  }

有3种情况需要建立普通连接:

  • 无代理
  • 明文的HTTP代理
  • SOCKS代理

普通连接的建立过程为建立TCP连接,建立TCP连接的过程为:

  • 1、创建Socket,非SOCKS代理的情况下,通过SocketFactory创建;在SOCKS代理则传入proxy手动new一个出来。
  • 2、为Socket设置超时
  • 3、完成特定于平台的连接建立
  • 4、创建用于I/O的source和sink

下面我来看下connectSocket()的具体实现,connectSocket()具体实现是AndroidPlatform.java里面的connectSocket()。
关于AndroidPlatform.java请看上一篇文章。

设置了SOCKS代理的情况下,仅有的特别之处在于,是通过传入proxy手动创建Socket。route的socketAddress包含目标HTTP服务器的域名。由此可见SOCKS协议的处理,主要是在Java标准库的java.net.Socket中处理,对于外界而言,就好像是HTTP服务器直接建立连接一样,因此连接时传入的地址都是HTTP服务器的域名。

而对于明文的HTTP代理的情况下,这里灭有任何特殊处理。route的socketAddress包含着代理服务器的IP地址。HTTP代理自身会根据请求及相应的实际内容,建立与HTTP服务器的TCP连接,并转发数据。

这时候我们再来看下建立隧道逻辑:

  /**
   * Does all the work to build an HTTPS connection over a proxy tunnel. The catch here is that a
   * proxy server can issue an auth challenge and then close the connection.
   */
  private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
      throws IOException {
    Request tunnelRequest = createTunnelRequest();
    HttpUrl url = tunnelRequest.url();
    int attemptedConnections = 0;
    int maxAttempts = 21;
    while (true) {
      if (++attemptedConnections > maxAttempts) {
        throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
      }

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

      if (tunnelRequest == null) break; // Tunnel successfully created.

      // The proxy decided to close the connection after an auth challenge. We need to create a new
      // connection, but this time with the auth credentials.
      closeQuietly(rawSocket);
      rawSocket = null;
      sink = null;
      source = null;
    }
  }

建立隧道连接的过程又分为几个步骤:

  • 创建隧道请求
  • 建立Socket连接
  • 发送请求建立隧道

隧道请求是一个常规的HTTP请求,只是请求的内容有点特殊。最初创建的隧道请求如:

  /**
   * Returns a request that creates a TLS tunnel via an HTTP proxy. Everything in the tunnel request
   * is sent unencrypted to the proxy server, so tunnels include only the minimum set of headers.
   * This avoids sending potentially sensitive data like HTTP cookies to the proxy unencrypted.
   */
  private Request createTunnelRequest() {
    return new Request.Builder()
        .url(route.address().url())
        .header("Host", Util.hostHeader(route.address().url(), true))
        .header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
        .header("User-Agent", Version.userAgent())
        .build();
  }

一个隧道请求的例子如下:

隧道.png

请求的"Host" header中包含了目标HTTP服务器的域名。建立socket连接的过程这里就不细说了

创建隧道的过程是这样子的:

/**
   * To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create
   * the proxy connection. This may need to be retried if the proxy requires authorization.
   */
  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) {
      Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink);
      source.timeout().timeout(readTimeout, MILLISECONDS);
      sink.timeout().timeout(writeTimeout, MILLISECONDS);
      tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
      tunnelConnection.finishRequest();
      Response response = tunnelConnection.readResponseHeaders(false)
          .request(tunnelRequest)
          .build();
      // The response body from a CONNECT should be empty, but if it is not then we should consume
      // it before proceeding.
      long contentLength = HttpHeaders.contentLength(response);
      if (contentLength == -1L) {
        contentLength = 0L;
      }
      Source body = tunnelConnection.newFixedLengthSource(contentLength);
      Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
      body.close();

      switch (response.code()) {
        case HTTP_OK:
          // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
          // that happens, then we will have buffered bytes that are needed by the SSLSocket!
          // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
          // that it will almost certainly fail because the proxy has sent unexpected data.
          if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
            throw new IOException("TLS tunnel buffered too many bytes!");
          }
          return null;

        case HTTP_PROXY_AUTH:
          tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response);
          if (tunnelRequest == null) throw new IOException("Failed to authenticate with proxy");

          if ("close".equalsIgnoreCase(response.header("Connection"))) {
            return tunnelRequest;
          }
          break;

        default:
          throw new IOException(
              "Unexpected response code for CONNECT: " + response.code());
      }
    }
  }

在前面创建的TCP连接值上,完成代理服务器的HTTP请求/响应交互。请求的内容类似下面这样:

"CONNECT m.taobao.com:443 HTTP/1.1"

这里可能会根据HTTP代理是否需要认证而有多次HTTP请求/响应交互。
总结一下OkHttp3中代理相关的处理;

  • 1、没有设置代理的情况下,直接与HTTP服务器建立TCP连接,然后进行HTTP请求/响应的交互。
  • 2、设置了SOCKS代理的情况下,创建Socket时,为其传入proxy,连接时还是以HTTP服务器为目标。在标准库的Socket中完成SOCKS协议相关的处理。此时基本上感知不到代理的存在。
  • 3、设置了HTTP代理时的HTTP请求,与HTTP代理服务器建立TCP连接。HTTP代理服务器解析HTTP请求/响应的内容,并根据其中的信息来完成数据的转发。也就是说,如果HTTP请求中不包含"Host"header,则有可能在设置了HTTP代理的情况下无法与HTTP服务器建立连接。
  • 4、HTTP代理时的HTTPS/HTTP2请求,与HTTP服务器建立通过HTTP代理的隧道连接。HTTP代理不再解析传输的数据,仅仅完成数据转发的功能。此时HTTP代理的功能退化为如同SOCKS代理类似。
  • 5、设置了代理类时,HTTP的服务器的域名解析会交给代理服务器执行。其中设置了HTTP代理时,会对HTTP代理的域名做域名解析。

上述流程弄明白后,来看下建立协议
不管是建立隧道连接,还是建立普通连接,都少不了建立协议这一步骤,这一步是建立好TCP连接之后,而在该TCP能被拿来手法数据之前执行的。它主要为了数据的加密传输做一些初始化,比如TCL握手,HTTP/2的协商。

  private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    //如果不是ssl
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }
    //如果是sll
    connectTls(connectionSpecSelector);
   //如果是HTTP2
    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();
    }
  }

上面的代码大体上可以归纳为两点

  • 1、对于加密的数据传输,创建TLS连接。对于明文传输,则设置protocol和socket。socket直接指向应用层,如HTTP或HTTP/2,交互的Socket。
    1.1对于明文传输没有设置HTTP代理的HTTP请求,它是与HTTP服务器之间的TCP socket。
    1.2对于加密传输没有设置HTTP代理服务器的HTTP或HTTP2请求,它是与HTTP服务器之间的SSLSocket。
    1.3对于加密传输设置了HTTP代理服务器的HTTP或HTTP2请求,它是与HTTP服务器之间经过代理服务器的SSLSocket,一个隧道连接;
    1.4对于加密传输设置了SOCKS代理的HTTP或HTTP2请求,它是一条经过了代理服务器的SSLSocket连接。
  • 2、对于HTTP/2,通过new 一个Http2Connection.Builder会建立HTTP/2连接 Http2Connection,然后执行http2Connection.start()和服务器建立协议。我们先来看下建立TLS连接的connectTls()方法
 private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      //在原来的Socket加一层ssl
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      sslSocket.startHandshake();
      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

      // Verify that the socket's certificates are acceptable for the target host.
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n    certificate: " + CertificatePinner.pin(cert)
            + "\n    DN: " + cert.getSubjectDN().getName()
            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
  }

TLS连接是对原始TCP连接的一个封装,以及听过TLS握手,及数据手法过程中的加解密等功能。在Java中,用SSLSocket来描述。上面建立的TLS连接的过程大体为:

  • 1、用SSLSocketFactory基于原始的TCP Socket,创建一个SSLSocket。
  • 2、并配置SSLSocket。
  • 3、在前面选择的ConnectionSpec支持TLS扩展参数时,配置TLS扩展参数。
  • 4、启动TLS握手
  • 5、TLS握手完成之后,获取证书信息。
  • 6、对TLS握手过程中传回来的证书进行验证。
  • 7、在前面选择的ConnectionSpec支持TLS扩展参数时,获取TLS握手过程中顺便完成的协议协商过程所选择的协议。这个过程主要用于HTTP/2的ALPN扩展。
  • 8、OkHttp主要使用Okio来做IO操作,这里会基于前面获取到SSLSocket创建于执行的IO的BufferedSource和BufferedSink等,并保存握手信息以及所选择的协议。

至此连接已经建立连接已经结束了。

这里说一下isHealthy(boolean doExtensiveChecks)方法,入参是一个布尔类,表示是否需要额外的检查。这里主要是检查,判断这个连接是否是健康的连接,即是否可以重用。那我们来看下

/** Returns true if this connection is ready to host new streams. */
  public boolean isHealthy(boolean doExtensiveChecks) {
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
      return false;
    }

    if (http2Connection != null) {
      return !http2Connection.isShutdown();
    }

    if (doExtensiveChecks) {
      try {
        int readTimeout = socket.getSoTimeout();
        try {
          socket.setSoTimeout(1);
          if (source.exhausted()) {
            return false; // Stream is exhausted; socket is closed.
          }
          return true;
        } finally {
          socket.setSoTimeout(readTimeout);
        }
      } catch (SocketTimeoutException ignored) {
        // Read timed out; socket is good.
      } catch (IOException e) {
        return false; // Couldn't read; socket is closed.
      }
    }
    return true;
  }

看上述代码可知,同时满足如下条件才是健康的连接,否则返回false

  • 1、socket已经关闭
  • 2、输入流关闭
  • 3、输出流关闭
  • 4、如果是HTTP/2连接,则HTTP/2连接也要关闭。

让我们再来看下isEligible(Address, Route)方法,这个方法主要是判断面对给出的addres和route,这个realConnetion是否可以重用。

/**
   * Returns true if this connection can carry a stream allocation to {@code address}. If non-null
   * {@code route} is the resolved route for a connection.
   */
  public boolean isEligible(Address address, Route route) {
    // If this connection is not accepting new streams, we're done.
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    // If the non-host fields of the address don't overlap, we're done.
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    // If the host exactly matches, we're done: this connection can carry the address.
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }

    // At this point we don't have a hostname match. But we still be able to carry the request if
    // our connection coalescing requirements are met. See also:
    // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
    // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

    // 1. This connection must be HTTP/2.
    if (http2Connection == null) return false;

    // 2. The routes must share an IP address. This requires us to have a DNS address for both
    // hosts, which only happens after route planning. We can't coalesce connections that use a
    // proxy, since proxies don't tell us the origin server's IP address.
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    // 3. This connection's server certificate's must cover the new host.
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    // 4. Certificate pinning must match the host.
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }

    return true; // The caller's address can be carried by this connection.
  }

判断逻辑如下:

最后再来看下newCodec(OkHttpClient, StreamAllocation)方法

    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/2则new一个Http2Codec。如果不是HTTP/2则new一个Http1Codec。

上面提到了connection的跟踪状态由ConncetionPool来管理。

二、ConnectionPool

大家先来看下一个类的注释

/**
 * Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
 * share the same {@link Address} may share a {@link Connection}. This class implements the policy
 * of which connections to keep open for future use.
 */

简单的翻译下,如下:
管理http和http/2的链接,以便减少网络请求延迟。同一个address将共享同一个connection。该类实现了复用连接的目标。

然后看下这个类的字段:

/**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   */
  //这是一个用于清楚过期链接的线程池,每个线程池最多只能运行一个线程,并且这个线程池允许被垃圾回收
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  //每个address的最大空闲连接数。
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //清理任务
  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };
  //链接的双向队列
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  //路由的数据库
  final RouteDatabase routeDatabase = new RouteDatabase();
   //清理任务正在执行的标志
  boolean cleanupRunning;

来看下它的属性,

  • 1、主要就是connections,可见ConnectionPool内部以队列方式存储连接;
  • 2、routDatabase是一个黑名单,用来记录不可用的route,但是看代码貌似ConnectionPool并没有使用它。所以此处不做分析。
  • 3、剩下的就是和清理有关了,所以executor是清理任务的线程池,cleanupRunning是清理任务的标志,cleanupRunnable是清理任务。

再来看下他的构造函数

  /**
   * Create a new connection pool with tuning parameters appropriate for a single-user application.
   * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
   * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
   */
 //创建一个适用于单个应用程序的新连接池。
 //该连接池的参数将在未来的okhttp中发生改变
 //目前最多可容乃5个空闲的连接,存活期是5分钟
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    //保持活着的时间,否则清理将旋转循环
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

通过这个构造器我们知道了这个连接池最多维持5个连接,且每个链接最多活5分钟。并且包含一个线程池包含一个清理任务。
所以maxIdleConnections和keepAliveDurationNs则是清理中淘汰连接的的指标,这里需要说明的是maxIdleConnections是值每个地址上最大的空闲连接数。所以OkHttp只是限制与同一个远程服务器的空闲连接数量,对整体的空闲连接并没有限制。

PS:

这时候说下ConnectionPool的实例化的过程,一个OkHttpClient只包含一个ConnectionPool,其实例化也是在OkHttpClient的过程。这里说一下ConnectionPool各个方法的调用并没有直接对外暴露,而是通过OkHttpClient的Internal接口统一对外暴露。

然后我们来看下他的get和put方法

  /**
   * Returns a recycled connection to {@code address}, or null if no such connection exists. The
   * route is null if the address has not yet been routed.
   */
  RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    //断言,判断线程是不是被自己锁住了
    assert (Thread.holdsLock(this));
    // 遍历已有连接集合
    for (RealConnection connection : connections) { 
       //如果connection和需求中的"地址"和"路由"匹配
      if (connection.isEligible(address, route)) {
       //复用这个连接
        streamAllocation.acquire(connection);
        //返回这个连接
        return connection;
      }
    }
    return null;
  }

get() 方法遍历 connections 中的所有 RealConnection 寻找同时满足条件的RealConnection。

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

put方法更为简单,就是异步触发清理任务,然后将连接添加到队列中。那么下面开始重点分析他的清理任务。

  private final long keepAliveDurationNs;
  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };

这个逻辑也很简单,就是调用cleanup方法执行清理,并等待一段时间,持续清理,其中cleanup方法返回的值来来决定而等待的时间长度。那我们继续来看下cleanup函数:

  /**
   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
   * either it has exceeded the keep alive limit or the idle connections limit.
   *
   * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
   * -1 if no further cleanups are required.
   */
  long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use, keep searching.
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        //统计空闲连接数量
        idleConnectionCount++;

        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          //找出空闲时间最长的连接以及对应的空闲时间
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }

      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
       //在符合清理条件下,清理空闲时间最长的连接
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        // A connection will be ready to evict soon.
        //不符合清理条件,则返回下次需要执行清理的等待时间,也就是此连接即将到期的时间
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
       //没有空闲的连接,则隔keepAliveDuration(分钟)之后再次执行
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
       //清理结束
        cleanupRunning = false;
        return -1;
      }
    }
    //关闭socket资源
    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
     //这里是在清理一个空闲时间最长的连接以后会执行到这里,需要立即再次执行清理
    return 0;
  }

  

这里的首先统计空闲连接数量,然后通过for循环查找最长空闲时间的连接以及对应空闲时长,然后判断是否超出最大空闲连接数(maxIdleConnections)或者或者超过最大空闲时间(keepAliveDurationNs),满足其一则清除最长空闲时长的连接。如果不满足清理条件,则返回一个对应等待时间。
这个对应等待的时间又分二种情况:

  • 1 有连接则等待下次需要清理的时间去清理:keepAliveDurationNs-longestIdleDurationNs;
  • 2 没有空闲的连接,则等下一个周期去清理:keepAliveDurationNs

如果清理完毕返回-1。
综上所述,我们来梳理一下清理任务,清理任务就是异步执行的,遵循两个指标,最大空闲连接数量和最大空闲时长,满足其一则清理空闲时长最大的那个连接,然后循环执行,要么等待一段时间,要么继续清理下一个连接,知道清理所有连接,清理任务才结束,下一次put的时候,如果已经停止的清理任务则会被再次触发

/**
   * Prunes any leaked allocations and then returns the number of remaining live allocations on
   * {@code connection}. Allocations are leaked if the connection is tracking them but the
   * application code has abandoned them. Leak detection is imprecise and relies on garbage
   * collection.
   */
  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<StreamAllocation>> references = connection.allocations;
     //遍历弱引用列表
    for (int i = 0; i < references.size(); ) {
      Reference<StreamAllocation> reference = references.get(i);
       //若StreamAllocation被使用则接着循环
      if (reference.get() != null) {
        i++;
        continue;
      }

      // We've discovered a leaked allocation. This is an application bug.
      StreamAllocation.StreamAllocationReference streamAllocRef =
          (StreamAllocation.StreamAllocationReference) reference;
      String message = "A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?";
      Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
       //若StreamAllocation未被使用则移除引用,这边注释为泄露
      references.remove(i);
      connection.noNewStreams = true;

      // If this was the last allocation, the connection is eligible for immediate eviction.
      //如果列表为空则说明此连接没有被引用了,则返回0,表示此连接是空闲连接
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }
    return references.size();
  }

pruneAndGetAllocationCount主要是用来标记泄露连接的。内部通过遍历传入进来的RealConnection的StreamAllocation列表,如果StreamAllocation被使用则接着遍历下一个StreamAllocation。如果StreamAllocation未被使用则从列表中移除,如果列表中为空则说明此连接连接没有引用了,返回0,表示此连接是空闲连接,否则就返回非0表示此连接是活跃连接。
接下来让我看下ConnectionPool的connectionBecameIdle()方法,就是当有连接空闲时,唤起cleanup线程清洗连接池

  /**
   * Notify this pool that {@code connection} has become idle. Returns true if the connection has
   * been removed from the pool and should be closed.
   */
  boolean connectionBecameIdle(RealConnection connection) {
    assert (Thread.holdsLock(this));
     //该连接已经不可用
    if (connection.noNewStreams || maxIdleConnections == 0) {
      connections.remove(connection);
      return true;
    } else {
      //欢迎clean 线程
      notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
      return false;
    }
  }

connectionBecameIdle标示一个连接处于空闲状态,即没有流任务,那么久需要调用该方法,由ConnectionPool来决定是否需要清理该连接。
再来看下deduplicate()方法

  /**
   * Replaces the connection held by {@code streamAllocation} with a shared connection if possible.
   * This recovers when multiple multiplexed connections are created concurrently.
   */
  Socket deduplicate(Address address, StreamAllocation streamAllocation) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, null)
          && connection.isMultiplexed()
          && connection != streamAllocation.connection()) {
        return streamAllocation.releaseAndAcquire(connection);
      }
    }
    return null;
  }

该方法主要是针对HTTP/2场景下多个多路复用连接清除的场景。如果是当前连接是HTTP/2,那么所有指向该站点的请求都应该基于同一个TCP连接。这个方法比较简单就不详细说了,再说下另外一个方法

  /** Close and remove all idle connections in the pool. */
  public void evictAll() {
    List<RealConnection> evictedConnections = new ArrayList<>();
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        if (connection.allocations.isEmpty()) {
          connection.noNewStreams = true;
          evictedConnections.add(connection);
          i.remove();
        }
      }
    }
    for (RealConnection connection : evictedConnections) {
      closeQuietly(connection.socket());
    }
  }

该方法是删除所有空闲的连接,比较简单,不说了

三、 StreamAllocation

这个类很重要,我们先来看下类的注释

/**
 * This class coordinates the relationship between three entities:
 *
 * <ul>
 *     <li><strong>Connections:</strong> physical socket connections to remote servers. These are
 *         potentially slow to establish so it is necessary to be able to cancel a connection
 *         currently being connected.
 *     <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
 *         connections. Each connection has its own allocation limit, which defines how many
 *         concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
 *         at a time, HTTP/2 typically carry multiple.
 *     <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
 *         its follow up requests. We prefer to keep all streams of a single call on the same
 *         connection for better behavior and locality.
 * </ul>
 *
 * <p>Instances of this class act on behalf of the call, using one or more streams over one or more
 * connections. This class has APIs to release each of the above resources:
 *
 * <ul>
 *     <li>{@link #noNewStreams()} prevents the connection from being used for new streams in the
 *         future. Use this after a {@code Connection: close} header, or when the connection may be
 *         inconsistent.
 *     <li>{@link #streamFinished streamFinished()} releases the active stream from this allocation.
 *         Note that only one stream may be active at a given time, so it is necessary to call
 *         {@link #streamFinished streamFinished()} before creating a subsequent stream with {@link
 *         #newStream newStream()}.
 *     <li>{@link #release()} removes the call's hold on the connection. Note that this won't
 *         immediately free the connection if there is a stream still lingering. That happens when a
 *         call is complete but its response body has yet to be fully consumed.
 * </ul>
 *
 * <p>This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have the
 * smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that stream
 * but not the other streams sharing its connection. But if the TLS handshake is still in progress
 * then canceling may break the entire connection.
 */

在讲解这个类的时候不得不说下背景:
HTTP的版本:
HTTP的版本从最初的1.0版本,到后续的1.1版本,再到后续的google推出的SPDY,后来再推出2.0版本,http协议越来越完善。(ps:okhttp也是根据2.0和1.1/1.0作为区分,实现了两种连接机制)这里要说下http2.0和http1.0,1.1的主要区别,2.0解决了老版本(1.1和1.0)最重要两个问题:连接无法复用和head of line blocking (HOL)问题.2.0使用多路复用的技术,多个stream可以共用一个socket连接,每个tcp连接都是通过一个socket来完成的,socket对应一个host和port,如果有多个stream(也就是多个request)都是连接在一个host和port上,那么它们就可以共同使用同一个socket,这样做的好处就是可以减少TCP的一个三次握手的时间。在OKHttp里面,记录一次连接的是RealConnection,这个负责连接,在这个类里面用socket来连接,用HandShake来处理握手。

在讲解这个类的之前我们先熟悉3个概念:请求、连接、流。我们要明白HTTP通信执行网络"请求"需要在"连接"上建立一个新的"流",我们将StreamAllocation称之流的桥梁,它负责为一次"请求"寻找"连接"并建立"流",从而完成远程通信。所以说StreamAllocation与"请求"、"连接"、"流"都有关。

从注释我们看到。Connection是建立在Socket之上的物流通信信道,而Stream则是代表逻辑的流,至于Call是对一次请求过程的封装。之前也说过一个Call可能会涉及多个流(比如重定向或者auth认证等情况)。所以我们想一下,如果StreamAllocation要想解决上述问题,需要两个步骤,一是寻找连接,二是获取流。所以StreamAllocation里面应该包含一个Stream(上文已经说到了,OKHttp里面的流是HttpCodec);还应该包含连接Connection。如果想找到合适的刘姐,还需要一个连接池ConnectionPool属性。所以应该有一个获取流的方法在StreamAllocation里面是newStream();找到合适的流的方法findConnection();还应该有完成请求任务的之后finish()的方法来关闭流对象,还有终止和取消等方法,以及释放资源的方法。

1、那咱们先就看下他的属性

  public final Address address;//地址
  private Route route; //路由
  private final ConnectionPool connectionPool;  //连接池
  private final Object callStackTrace; //日志

  // State guarded by connectionPool.
  private final RouteSelector routeSelector; //路由选择器
  private int refusedStreamCount;  //拒绝的次数
  private RealConnection connection;  //连接
  private boolean released;  //是否已经被释放
  private boolean canceled  //是否被取消了

看完属性,我们来看下构造函数


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

这时候我们再来看下他的一个比较重要的方法

  public HttpCodec 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);
   //实例化HttpCodec,如果是HTTP/2则是Http2Codec否则是Http1Codec
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

这里面两个重要方法
1是通过findHealthyConnection获取一个连接、2是通过resultConnection.newCodec获取流。
我们接着来看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 {
    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;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

我们看到里面调用findConnection来获取一个RealConnection,然后通过RealConnection自己的方法isHealthy,去判断是否是健康的连接,如果是健康的连接,则重用,否则就继续查找。那我们继续看下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 {
    Route selectedRoute;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");
      //获取存在的连接
      // Attempt to use an already-allocated connection.
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
          // 如果已经存在的连接满足要求,则使用已存在的连接
        return allocatedConnection;
      }
      //从缓存中去取
      // Attempt to get a connection from the pool.
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }
       // 线路的选择,多ip的支持
    // If we need a route, make one. This is a blocking operation.
    if (selectedRoute == null) {
      //里面是个递归
      selectedRoute = routeSelector.next();
    }

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

      // Now that we have an IP address, make another attempt at getting a connection from the pool.
      // This could match due to connection coalescing.
      //更换路由再次尝试
      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) return connection;

      // Create a connection and assign it to this allocation immediately. This makes it possible
      // for an asynchronous cancel() to interrupt the handshake we're about to do.
      route = selectedRoute;
      refusedStreamCount = 0;
     // 以上都不符合,创建一个连接
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result);
    }
     //连接并握手
    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    //更新本地数据库
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // Pool the connection.
      //把连接放到连接池中
      Internal.instance.put(connectionPool, result);
      //如果这个连接是多路复用
      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        //调用connectionPool的deduplicate方法去重。
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    //如果是重复的socket则关闭socket,不是则socket为nul,什么也不做
    closeQuietly(socket);
    //返回整个连接
    return result;
  }

上面代码大概的逻辑是:

  • 1、先找是否有已经存在的连接,如果有已经存在的连接,并且可以使用(!noNewStreams)则直接返回。
  • 2、根据已知的address在connectionPool里面找,如果有连接,则返回
  • 3、更换路由,更换线路,在connectionPool里面再次查找,如果有则返回。
  • 4、如果以上条件都不满足则直接new一个RealConnection出来
  • 5、new出来的RealConnection通过acquire关联到connection.allocations上
  • 6、做去重判断,如果有重复的socket则关闭

里面涉及到的RealConnection的connect()方法,我们已经在RealConnection里面讲过,这里就不讲了。不过这里说下acquire()方法

  /**
   * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
   * {@link #release} on the same connection.
   */
  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));
  }

这里相当于给connection的引用计数器加1
这里说下StreamAllocationReference,StreamAllocationReference其实是弱引用的子类。具体代码如下:

  public static final class StreamAllocationReference extends WeakReference<StreamAllocation> {
    /**
     * Captures the stack trace at the time the Call is executed or enqueued. This is helpful for
     * identifying the origin of connection leaks.
     */
    public final Object callStackTrace;

    StreamAllocationReference(StreamAllocation referent, Object callStackTrace) {
      super(referent);
      this.callStackTrace = callStackTrace;
    }
  }

下面来看下他的他的其他方法streamFinished(boolean, HttpCodec)、release(RealConnection)和deallocate(boolean, boolean, boolean)方法。

  public void streamFinished(boolean noNewStreams, HttpCodec codec) {
    Socket socket;
    synchronized (connectionPool) {
      if (codec == null || codec != this.codec) {
        throw new IllegalStateException("expected " + this.codec + " but was " + codec);
      }
      if (!noNewStreams) {
        connection.successCount++;
      }
      socket = deallocate(noNewStreams, false, true);
    }
    closeQuietly(socket);
  }

/**
   * Releases resources held by this allocation. If sufficient resources are allocated, the
   * connection will be detached or closed. Callers must be synchronized on the connection pool.
   *
   * <p>Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion
   * of the synchronized block. (We don't do I/O while synchronized on the connection pool.)
   */
  private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        connection.noNewStreams = true;
      }
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        connection = null;
      }
    }
    return socket;
  }

  /** Remove this allocation from the connection's list of allocations. */
  private void release(RealConnection connection) {
    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
      Reference<StreamAllocation> reference = connection.allocations.get(i);
      if (reference.get() == this) {
        connection.allocations.remove(i);
        return;
      }
    }
    throw new IllegalStateException();
  }

其中deallocate(boolean, boolean, boolean)和release(RealConnection)方法都是private,而且均在streamFinished里面调用。
release(RealConnection)方法比较简单,主要是把RealConnection对应的allocations清除掉,把计数器归零。
deallocate(boolean, boolean, boolean)方法也简单,根据传入的三个布尔类型的值进行操作,如果streamFinished为true则代表关闭流,所以要通过连接池connectionPool把这个connection设置空闲连接,如果可以设为空闲连接则返回这个socket。不能则返回null。
streamFinished()主要做了一些异常判断,然后调用deallocate()方法
综上所述:streamFinished(boolean, HttpCodec)主要是关闭流,release(RealConnection)主要是释放connection的引用,deallocate(boolean, boolean, boolean)主要是根据参数做一些设置。
上面说到了release(RealConnection),为了防止大家混淆概念,这里说一下另外一个方法release()这个是无参的方法。

  public void release() {
    Socket socket;
    synchronized (connectionPool) {
      socket = deallocate(false, true, false);
    }
    closeQuietly(socket);
  }

注意这个和上面的带有RealConnection的参数release()的区别。
然后说一下noNewStreams()方法,主要是设置防止别人在这个连接上开新的流。

  /** Forbid new streams from being created on the connection that hosts this allocation. */
  public void noNewStreams() {
    Socket socket;
    synchronized (connectionPool) {
      socket = deallocate(true, false, false);
    }
    closeQuietly(socket);
  }

还有一个方法,平时也是经常有遇到的就是cancel()方法

  public void cancel() {
    HttpCodec codecToCancel;
    RealConnection connectionToCancel;
    synchronized (connectionPool) {
      canceled = true;
      codecToCancel = codec;
      connectionToCancel = connection;
    }
    if (codecToCancel != null) {
      codecToCancel.cancel();
    } else if (connectionToCancel != null) {
      connectionToCancel.cancel();
    }
  }

其实也比较简单的就是调用RealConnection的Cancel方法。
如果在连接中过程出现异常,会调用streamFailed(IOException)方法

public void streamFailed(IOException e) {
    Socket socket;
    boolean noNewStreams = false;

    synchronized (connectionPool) {
      if (e instanceof StreamResetException) {
        StreamResetException streamResetException = (StreamResetException) e;
        if (streamResetException.errorCode == ErrorCode.REFUSED_STREAM) {
          refusedStreamCount++;
        }
        // On HTTP/2 stream errors, retry REFUSED_STREAM errors once on the same connection. All
        // other errors must be retried on a new connection.
        if (streamResetException.errorCode != ErrorCode.REFUSED_STREAM || refusedStreamCount > 1) {
          noNewStreams = true;
          route = null;
        }
      } else if (connection != null
          && (!connection.isMultiplexed() || e instanceof ConnectionShutdownException)) {
        noNewStreams = true;

        // If this route hasn't completed a call, avoid it for new connections.
        if (connection.successCount == 0) {
          if (route != null && e != null) {
            routeSelector.connectFailed(route, e);
          }
          route = null;
        }
      }
      socket = deallocate(noNewStreams, false, true);
    }

    closeQuietly(socket);
  }

根据异常类型来采取不同的应对措施。注释已经比较清楚了,就不细说了。
其他的方法比较简单,我这里就不细说了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容