OKHTTP连接中三个"核心"RealConnection、ConnectionPool、StreamAllocation

转自:http://www.manongjc.com/detail/52-wrccbcsrfqkhzpg.html

参考:

http://events.jianshu.io/p/7dc2d6577b10

https://www.jianshu.com/p/c584fb47bc69

https://www.jianshu.com/p/3d8fa2c60b6b

https://www.cnblogs.com/lujiango/p/11771319.html

https://blog.csdn.net/hedgehog_ming/article/details/78657655

一、RealConnection

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

 1 private final ConnectionPool connectionPool;
 2   private final Route route;
 3 
 4   // The fields below are initialized by connect() and never reassigned.
 5   //下面这些字段,通过connect()方法开始初始化,并且绝对不会再次赋值
 6   /** The low-level TCP socket. */
 7   private Socket rawSocket; //底层socket
 8   /**
 9    * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
10    * {@link #rawSocket} itself if this connection does not use SSL.
11    */
12   private Socket socket;  //应用层socket
13   //握手
14   private Handshake handshake;
15    //协议
16   private Protocol protocol;
17    // http2的链接
18   private Http2Connection http2Connection;
19   //通过source和sink,大家可以猜到是与服务器交互的输入输出流
20   private BufferedSource source;
21   private BufferedSink sink;
22 
23   // The fields below track connection state and are guarded by connectionPool.
24   //下面这个字段是 属于表示链接状态的字段,并且有connectPool统一管理
25   /** If true, no new streams can be created on this connection. Once true this is always true. */
26   //如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
27   public boolean noNewStreams;
28   
29   //成功的次数
30   public int successCount;
31 
32   /**
33    * The maximum number of concurrent streams that can be carried by this connection. If {@code
34    * allocations.size() < allocationLimit} then new streams can be created on this connection.
35    */
36   //此链接可以承载最大并发流的限制,如果不超过限制,可以随意增加
37   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()方法:

 1 public void connect( int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
 2     if (protocol != null) throw new IllegalStateException("already connected");
 3      // 线路的选择
 4     RouteException routeException = null;
 5     List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
 6     ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
 7 
 8     if (route.address().sslSocketFactory() == null) {
 9       if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
10         throw new RouteException(new UnknownServiceException(
11             "CLEARTEXT communication not enabled for client"));
12       }
13       String host = route.address().url().host();
14       if (!Platform.get().isCleartextTrafficPermitted(host)) {
15         throw new RouteException(new UnknownServiceException(
16             "CLEARTEXT communication to " + host + " not permitted by network security policy"));
17       }
18     }
19     // 连接开始
20     while (true) {
21       try {
22         // 如果要求隧道模式,建立通道连接,通常不是这种
23         if (route.requiresTunnel()) {
24           connectTunnel(connectTimeout, readTimeout, writeTimeout);
25         } else {
26            // 一般都走这条逻辑了,实际上很简单就是socket的连接
27           connectSocket(connectTimeout, readTimeout);
28         }
29         // https的建立
30         establishProtocol(connectionSpecSelector);
31         break;
32       } catch (IOException e) {
33         closeQuietly(socket);
34         closeQuietly(rawSocket);
35         socket = null;
36         rawSocket = null;
37         source = null;
38         sink = null;
39         handshake = null;
40         protocol = null;
41         http2Connection = null;
42 
43         if (routeException == null) {
44           routeException = new RouteException(e);
45         } else {
46           routeException.addConnectException(e);
47         }
48 
49         if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
50           throw routeException;
51         }
52       }
53     }
54 
55     if (http2Connection != null) {
56       synchronized (connectionPool) {
57         allocationLimit = http2Connection.maxConcurrentStreams();
58       }
59     }
60   }

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

  • 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()方法

 1 /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
 2   private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
 3     Proxy proxy = route.proxy();
 4     Address address = route.address();
 5      // 根据代理类型来选择socket类型,是代理还是直连
 6     rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
 7         ? address.socketFactory().createSocket()
 8         : new Socket(proxy);
 9 
10     rawSocket.setSoTimeout(readTimeout);
11     try {
12       // 连接socket,之所以这样写是因为支持不同的平台
13       //里面实际上是  socket.connect(address, connectTimeout);
14       Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
15     } catch (ConnectException e) {
16       ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
17       ce.initCause(e);
18       throw ce;
19     }
20      // 得到输入/输出流
21     source = Okio.buffer(Okio.source(rawSocket));
22     sink = Okio.buffer(Okio.sink(rawSocket));
23   }

有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()。

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

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

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

 1 /**
 2    * Does all the work to build an HTTPS connection over a proxy tunnel. The catch here is that a
 3    * proxy server can issue an auth challenge and then close the connection.
 4    */
 5   private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
 6       throws IOException {
 7     Request tunnelRequest = createTunnelRequest();
 8     HttpUrl url = tunnelRequest.url();
 9     int attemptedConnections = 0;
10     int maxAttempts = 21;
11     while (true) {
12       if (++attemptedConnections > maxAttempts) {
13         throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
14       }
15 
16       connectSocket(connectTimeout, readTimeout);
17       tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
18 
19       if (tunnelRequest == null) break; // Tunnel successfully created.
20 
21       // The proxy decided to close the connection after an auth challenge. We need to create a new
22       // connection, but this time with the auth credentials.
23       closeQuietly(rawSocket);
24       rawSocket = null;
25       sink = null;
26       source = null;
27     }
28   }

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

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

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

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

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

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

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

 1 /**
 2    * To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create
 3    * the proxy connection. This may need to be retried if the proxy requires authorization.
 4    */
 5   private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,
 6       HttpUrl url) throws IOException {
 7     // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
 8     String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
 9     while (true) {
10       Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink);
11       source.timeout().timeout(readTimeout, MILLISECONDS);
12       sink.timeout().timeout(writeTimeout, MILLISECONDS);
13       tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
14       tunnelConnection.finishRequest();
15       Response response = tunnelConnection.readResponseHeaders(false)
16           .request(tunnelRequest)
17           .build();
18       // The response body from a CONNECT should be empty, but if it is not then we should consume
19       // it before proceeding.
20       long contentLength = HttpHeaders.contentLength(response);
21       if (contentLength == -1L) {
22         contentLength = 0L;
23       }
24       Source body = tunnelConnection.newFixedLengthSource(contentLength);
25       Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
26       body.close();
27 
28       switch (response.code()) {
29         case HTTP_OK:
30           // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
31           // that happens, then we will have buffered bytes that are needed by the SSLSocket!
32           // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
33           // that it will almost certainly fail because the proxy has sent unexpected data.
34           if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
35             throw new IOException("TLS tunnel buffered too many bytes!");
36           }
37           return null;
38 
39         case HTTP_PROXY_AUTH:
40           tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response);
41           if (tunnelRequest == null) throw new IOException("Failed to authenticate with proxy");
42 
43           if ("close".equalsIgnoreCase(response.header("Connection"))) {
44             return tunnelRequest;
45           }
46           break;
47 
48         default:
49           throw new IOException(
50               "Unexpected response code for CONNECT: " + response.code());
51       }
52     }
53   }

在前面创建的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的协商。

 1 private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
 2     //如果不是ssl
 3     if (route.address().sslSocketFactory() == null) {
 4       protocol = Protocol.HTTP_1_1;
 5       socket = rawSocket;
 6       return;
 7     }
 8     //如果是sll
 9     connectTls(connectionSpecSelector);
10    //如果是HTTP2
11     if (protocol == Protocol.HTTP_2) {
12       socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
13       http2Connection = new Http2Connection.Builder(true)
14           .socket(socket, route.address().url().host(), source, sink)
15           .listener(this)
16           .build();
17       http2Connection.start();
18     }
19   }

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

  • 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()方法
 1 private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
 2     Address address = route.address();
 3     SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
 4     boolean success = false;
 5     SSLSocket sslSocket = null;
 6     try {
 7       // Create the wrapper over the connected socket.
 8       //在原来的Socket加一层ssl
 9       sslSocket = (SSLSocket) sslSocketFactory.createSocket(
10           rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
11 
12       // Configure the socket's ciphers, TLS versions, and extensions.
13       ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
14       if (connectionSpec.supportsTlsExtensions()) {
15         Platform.get().configureTlsExtensions(
16             sslSocket, address.url().host(), address.protocols());
17       }
18 
19       // Force handshake. This can throw!
20       sslSocket.startHandshake();
21       Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
22 
23       // Verify that the socket's certificates are acceptable for the target host.
24       if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
25         X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
26         throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
27             + "n    certificate: " + CertificatePinner.pin(cert)
28             + "n    DN: " + cert.getSubjectDN().getName()
29             + "n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
30       }
31 
32       // Check that the certificate pinner is satisfied by the certificates presented.
33       address.certificatePinner().check(address.url().host(),
34           unverifiedHandshake.peerCertificates());
35 
36       // Success! Save the handshake and the ALPN protocol.
37       String maybeProtocol = connectionSpec.supportsTlsExtensions()
38           ? Platform.get().getSelectedProtocol(sslSocket)
39           : null;
40       socket = sslSocket;
41       source = Okio.buffer(Okio.source(socket));
42       sink = Okio.buffer(Okio.sink(socket));
43       handshake = unverifiedHandshake;
44       protocol = maybeProtocol != null
45           ? Protocol.get(maybeProtocol)
46           : Protocol.HTTP_1_1;
47       success = true;
48     } catch (AssertionError e) {
49       if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
50       throw e;
51     } finally {
52       if (sslSocket != null) {
53         Platform.get().afterHandshake(sslSocket);
54       }
55       if (!success) {
56         closeQuietly(sslSocket);
57       }
58     }
59   }

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)方法,入参是一个布尔类,表示是否需要额外的检查。这里主要是检查,判断这个连接是否是健康的连接,即是否可以重用。那我们来看下

 1 /** Returns true if this connection is ready to host new streams. */
 2   public boolean isHealthy(boolean doExtensiveChecks) {
 3     if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
 4       return false;
 5     }
 6 
 7     if (http2Connection != null) {
 8       return !http2Connection.isShutdown();
 9     }
10 
11     if (doExtensiveChecks) {
12       try {
13         int readTimeout = socket.getSoTimeout();
14         try {
15           socket.setSoTimeout(1);
16           if (source.exhausted()) {
17             return false; // Stream is exhausted; socket is closed.
18           }
19           return true;
20         } finally {
21           socket.setSoTimeout(readTimeout);
22         }
23       } catch (SocketTimeoutException ignored) {
24         // Read timed out; socket is good.
25       } catch (IOException e) {
26         return false; // Couldn't read; socket is closed.
27       }
28     }
29     return true;
30   }

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

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

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

 1 /**
 2    * Returns true if this connection can carry a stream allocation to {@code address}. If non-null
 3    * {@code route} is the resolved route for a connection.
 4    */
 5   public boolean isEligible(Address address, Route route) {
 6     // If this connection is not accepting new streams, we're done.
 7     if (allocations.size() >= allocationLimit || noNewStreams) return false;
 8 
 9     // If the non-host fields of the address don't overlap, we're done.
10     if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
11 
12     // If the host exactly matches, we're done: this connection can carry the address.
13     if (address.url().host().equals(this.route().address().url().host())) {
14       return true; // This connection is a perfect match.
15     }
16 
17     // At this point we don't have a hostname match. But we still be able to carry the request if
18     // our connection coalescing requirements are met. See also:
19     // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
20     // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
21 
22     // 1. This connection must be HTTP/2.
23     if (http2Connection == null) return false;
24 
25     // 2. The routes must share an IP address. This requires us to have a DNS address for both
26     // hosts, which only happens after route planning. We can't coalesce connections that use a
27     // proxy, since proxies don't tell us the origin server's IP address.
28     if (route == null) return false;
29     if (route.proxy().type() != Proxy.Type.DIRECT) return false;
30     if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
31     if (!this.route.socketAddress().equals(route.socketAddress())) return false;
32 
33     // 3. This connection's server certificate's must cover the new host.
34     if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
35     if (!supportsUrl(address.url())) return false;
36 
37     // 4. Certificate pinning must match the host.
38     try {
39       address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
40     } catch (SSLPeerUnverifiedException e) {
41       return false;
42     }
43 
44     return true; // The caller's address can be carried by this connection.
45   }

判断逻辑如下:

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

1     if (http2Connection != null) {
2       return new Http2Codec(client, streamAllocation, http2Connection);
3     } else {
4       socket.setSoTimeout(client.readTimeoutMillis());
5       source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
6       sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
7       return new Http1Codec(client, streamAllocation, source, sink);
8     }

里面主要是判断是否是HTTP/2,如果是HTTP/2则new一个Http2Codec。如果不是HTTP/2则new一个Http1Codec。

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

二、ConnectionPool

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

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

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

然后看下这个类的字段:

 1 /**
 2    * Background threads are used to cleanup expired connections. There will be at most a single
 3    * thread running per connection pool. The thread pool executor permits the pool itself to be
 4    * garbage collected.
 5    */
 6   //这是一个用于清楚过期链接的线程池,每个线程池最多只能运行一个线程,并且这个线程池允许被垃圾回收
 7   private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
 8       Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
 9       new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
10 
11   /** The maximum number of idle connections for each address. */
12   //每个address的最大空闲连接数。
13   private final int maxIdleConnections;
14   private final long keepAliveDurationNs;
15   //清理任务
16   private final Runnable cleanupRunnable = new Runnable() {
17     @Override public void run() {
18       while (true) {
19         long waitNanos = cleanup(System.nanoTime());
20         if (waitNanos == -1) return;
21         if (waitNanos > 0) {
22           long waitMillis = waitNanos / 1000000L;
23           waitNanos -= (waitMillis * 1000000L);
24           synchronized (ConnectionPool.this) {
25             try {
26               ConnectionPool.this.wait(waitMillis, (int) waitNanos);
27             } catch (InterruptedException ignored) {
28             }
29           }
30         }
31       }
32     }
33   };
34   //链接的双向队列
35   private final Deque<RealConnection> connections = new ArrayDeque<>();
36   //路由的数据库
37   final RouteDatabase routeDatabase = new RouteDatabase();
38    //清理任务正在执行的标志
39   boolean cleanupRunning;

来看下它的属性,

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

再来看下他的构造函数

 1 /**
 2    * Create a new connection pool with tuning parameters appropriate for a single-user application.
 3    * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
 4    * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
 5    */
 6  //创建一个适用于单个应用程序的新连接池。
 7  //该连接池的参数将在未来的okhttp中发生改变
 8  //目前最多可容乃5个空闲的连接,存活期是5分钟
 9   public ConnectionPool() {
10     this(5, 5, TimeUnit.MINUTES);
11   }
12 
13   public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
14     this.maxIdleConnections = maxIdleConnections;
15     this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
16 
17     // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
18     //保持活着的时间,否则清理将旋转循环
19     if (keepAliveDuration <= 0) {
20       throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
21     }
22   }

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

PS:

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

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

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

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

1 void put(RealConnection connection) {
2     assert (Thread.holdsLock(this));
3     if (!cleanupRunning) {
4       cleanupRunning = true;
5       executor.execute(cleanupRunnable);
6     }
7     connections.add(connection);
8   }

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

 1 private final long keepAliveDurationNs;
 2   private final Runnable cleanupRunnable = new Runnable() {
 3     @Override public void run() {
 4       while (true) {
 5         long waitNanos = cleanup(System.nanoTime());
 6         if (waitNanos == -1) return;
 7         if (waitNanos > 0) {
 8           long waitMillis = waitNanos / 1000000L;
 9           waitNanos -= (waitMillis * 1000000L);
10           synchronized (ConnectionPool.this) {
11             try {
12               ConnectionPool.this.wait(waitMillis, (int) waitNanos);
13             } catch (InterruptedException ignored) {
14             }
15           }
16         }
17       }
18     }
19   };

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

 1  /**
 2    * Performs maintenance on this pool, evicting the connection that has been idle the longest if
 3    * either it has exceeded the keep alive limit or the idle connections limit.
 4    *
 5    * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
 6    * -1 if no further cleanups are required.
 7    */
 8   long cleanup(long now) {
 9     int inUseConnectionCount = 0;
10     int idleConnectionCount = 0;
11     RealConnection longestIdleConnection = null;
12     long longestIdleDurationNs = Long.MIN_VALUE;
13 
14     // Find either a connection to evict, or the time that the next eviction is due.
15     synchronized (this) {
16       for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
17         RealConnection connection = i.next();
18 
19         // If the connection is in use, keep searching.
20         if (pruneAndGetAllocationCount(connection, now) > 0) {
21           inUseConnectionCount++;
22           continue;
23         }
24         //统计空闲连接数量
25         idleConnectionCount++;
26 
27         // If the connection is ready to be evicted, we're done.
28         long idleDurationNs = now - connection.idleAtNanos;
29         if (idleDurationNs > longestIdleDurationNs) {
30           //找出空闲时间最长的连接以及对应的空闲时间
31           longestIdleDurationNs = idleDurationNs;
32           longestIdleConnection = connection;
33         }
34       }
35 
36       if (longestIdleDurationNs >= this.keepAliveDurationNs
37           || idleConnectionCount > this.maxIdleConnections) {
38         // We've found a connection to evict. Remove it from the list, then close it below (outside
39         // of the synchronized block).
40        //在符合清理条件下,清理空闲时间最长的连接
41         connections.remove(longestIdleConnection);
42       } else if (idleConnectionCount > 0) {
43         // A connection will be ready to evict soon.
44         //不符合清理条件,则返回下次需要执行清理的等待时间,也就是此连接即将到期的时间
45         return keepAliveDurationNs - longestIdleDurationNs;
46       } else if (inUseConnectionCount > 0) {
47         // All connections are in use. It'll be at least the keep alive duration 'til we run again.
48        //没有空闲的连接,则隔keepAliveDuration(分钟)之后再次执行
49         return keepAliveDurationNs;
50       } else {
51         // No connections, idle or in use.
52        //清理结束
53         cleanupRunning = false;
54         return -1;
55       }
56     }
57     //关闭socket资源
58     closeQuietly(longestIdleConnection.socket());
59 
60     // Cleanup again immediately.
61      //这里是在清理一个空闲时间最长的连接以后会执行到这里,需要立即再次执行清理
62     return 0;
63   }

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

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

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

 1 /**
 2    * Prunes any leaked allocations and then returns the number of remaining live allocations on
 3    * {@code connection}. Allocations are leaked if the connection is tracking them but the
 4    * application code has abandoned them. Leak detection is imprecise and relies on garbage
 5    * collection.
 6    */
 7   private int pruneAndGetAllocationCount(RealConnection connection, long now) {
 8     List<Reference<StreamAllocation>> references = connection.allocations;
 9      //遍历弱引用列表
10     for (int i = 0; i < references.size(); ) {
11       Reference<StreamAllocation> reference = references.get(i);
12        //若StreamAllocation被使用则接着循环
13       if (reference.get() != null) {
14         i++;
15         continue;
16       }
17 
18       // We've discovered a leaked allocation. This is an application bug.
19       StreamAllocation.StreamAllocationReference streamAllocRef =
20           (StreamAllocation.StreamAllocationReference) reference;
21       String message = "A connection to " + connection.route().address().url()
22           + " was leaked. Did you forget to close a response body?";
23       Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
24        //若StreamAllocation未被使用则移除引用,这边注释为泄露
25       references.remove(i);
26       connection.noNewStreams = true;
27 
28       // If this was the last allocation, the connection is eligible for immediate eviction.
29       //如果列表为空则说明此连接没有被引用了,则返回0,表示此连接是空闲连接
30       if (references.isEmpty()) {
31         connection.idleAtNanos = now - keepAliveDurationNs;
32         return 0;
33       }
34     }
35     return references.size();
36   }

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

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

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

 1 /**
 2    * Replaces the connection held by {@code streamAllocation} with a shared connection if possible.
 3    * This recovers when multiple multiplexed connections are created concurrently.
 4    */
 5   Socket deduplicate(Address address, StreamAllocation streamAllocation) {
 6     assert (Thread.holdsLock(this));
 7     for (RealConnection connection : connections) {
 8       if (connection.isEligible(address, null)
 9           && connection.isMultiplexed()
10           && connection != streamAllocation.connection()) {
11         return streamAllocation.releaseAndAcquire(connection);
12       }
13     }
14     return null;
15   }

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

 1 /** Close and remove all idle connections in the pool. */
 2   public void evictAll() {
 3     List<RealConnection> evictedConnections = new ArrayList<>();
 4     synchronized (this) {
 5       for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
 6         RealConnection connection = i.next();
 7         if (connection.allocations.isEmpty()) {
 8           connection.noNewStreams = true;
 9           evictedConnections.add(connection);
10           i.remove();
11         }
12       }
13     }
14     for (RealConnection connection : evictedConnections) {
15       closeQuietly(connection.socket());
16     }
17   }

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

三、 StreamAllocation

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

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

在讲解这个类的时候不得不说下背景: 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、那咱们先就看下他的属性

 1   public final Address address;//地址
 2   private Route route; //路由
 3   private final ConnectionPool connectionPool;  //连接池
 4   private final Object callStackTrace; //日志
 5 
 6   // State guarded by connectionPool.
 7   private final RouteSelector routeSelector; //路由选择器
 8   private int refusedStreamCount;  //拒绝的次数
 9   private RealConnection connection;  //连接
10   private boolean released;  //是否已经被释放
11   private boolean canceled  //是否被取消了

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

1   public StreamAllocation(ConnectionPool connectionPool, Address address, Object callStackTrace) {
2     this.connectionPool = connectionPool;
3     this.address = address;
4     this.routeSelector = new RouteSelector(address, routeDatabase());
5     this.callStackTrace = callStackTrace;
6   }

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

 1 public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
 2     int connectTimeout = client.connectTimeoutMillis();
 3     int readTimeout = client.readTimeoutMillis();
 4     int writeTimeout = client.writeTimeoutMillis();
 5     boolean connectionRetryEnabled = client.retryOnConnectionFailure();
 6 
 7     try {
 8       //获取一个连接
 9       RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
10           writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
11    //实例化HttpCodec,如果是HTTP/2则是Http2Codec否则是Http1Codec
12       HttpCodec resultCodec = resultConnection.newCodec(client, this);
13 
14       synchronized (connectionPool) {
15         codec = resultCodec;
16         return resultCodec;
17       }
18     } catch (IOException e) {
19       throw new RouteException(e);
20     }
21   }

这里面两个重要方法 1是通过findHealthyConnection获取一个连接、2是通过resultConnection.newCodec获取流。 我们接着来看findHealthyConnection()方法

 
 1 /**
 2    * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
 3    * until a healthy connection is found.
 4    */
 5   private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
 6       int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
 7       throws IOException {
 8     while (true) {
 9       RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
10           connectionRetryEnabled);
11 
12       // If this is a brand new connection, we can skip the extensive health checks.
13       synchronized (connectionPool) {
14         if (candidate.successCount == 0) {
15           return candidate;
16         }
17       }
18 
19       // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
20       // isn't, take it out of the pool and start again.
21       if (!candidate.isHealthy(doExtensiveHealthChecks)) {
22         noNewStreams();
23         continue;
24       }
25 
26       return candidate;
27     }
28   }

我们看到里面调用findConnection来获取一个RealConnection,然后通过RealConnection自己的方法isHealthy,去判断是否是健康的连接,如果是健康的连接,则重用,否则就继续查找。那我们继续看下findConnection()方法

 1 /**
 2    * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 3    * then the pool, finally building a new connection.
 4    */
 5   private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
 6       boolean connectionRetryEnabled) throws IOException {
 7     Route selectedRoute;
 8     synchronized (connectionPool) {
 9       if (released) throw new IllegalStateException("released");
10       if (codec != null) throw new IllegalStateException("codec != null");
11       if (canceled) throw new IOException("Canceled");
12       //获取存在的连接
13       // Attempt to use an already-allocated connection.
14       RealConnection allocatedConnection = this.connection;
15       if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
16           // 如果已经存在的连接满足要求,则使用已存在的连接
17         return allocatedConnection;
18       }
19       //从缓存中去取
20       // Attempt to get a connection from the pool.
21       Internal.instance.get(connectionPool, address, this, null);
22       if (connection != null) {
23         return connection;
24       }
25 
26       selectedRoute = route;
27     }
28        // 线路的选择,多ip的支持
29     // If we need a route, make one. This is a blocking operation.
30     if (selectedRoute == null) {
31       //里面是个递归
32       selectedRoute = routeSelector.next();
33     }
34 
35     RealConnection result;
36     synchronized (connectionPool) {
37       if (canceled) throw new IOException("Canceled");
38 
39       // Now that we have an IP address, make another attempt at getting a connection from the pool.
40       // This could match due to connection coalescing.
41       //更换路由再次尝试
42       Internal.instance.get(connectionPool, address, this, selectedRoute);
43       if (connection != null) return connection;
44 
45       // Create a connection and assign it to this allocation immediately. This makes it possible
46       // for an asynchronous cancel() to interrupt the handshake we're about to do.
47       route = selectedRoute;
48       refusedStreamCount = 0;
49      // 以上都不符合,创建一个连接
50       result = new RealConnection(connectionPool, selectedRoute);
51       acquire(result);
52     }
53      //连接并握手
54     // Do TCP + TLS handshakes. This is a blocking operation.
55     result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
56     //更新本地数据库
57     routeDatabase().connected(result.route());
58 
59     Socket socket = null;
60     synchronized (connectionPool) {
61       // Pool the connection.
62       //把连接放到连接池中
63       Internal.instance.put(connectionPool, result);
64       //如果这个连接是多路复用
65       // If another multiplexed connection to the same address was created concurrently, then
66       // release this connection and acquire that one.
67       if (result.isMultiplexed()) {
68         //调用connectionPool的deduplicate方法去重。
69         socket = Internal.instance.deduplicate(connectionPool, address, this);
70         result = connection;
71       }
72     }
73     //如果是重复的socket则关闭socket,不是则socket为nul,什么也不做
74     closeQuietly(socket);
75     //返回整个连接
76     return result;
77   }

上面代码大概的逻辑是:

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

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

 1 /**
 2    * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
 3    * {@link #release} on the same connection.
 4    */
 5   public void acquire(RealConnection connection) {
 6     assert (Thread.holdsLock(connectionPool));
 7     if (this.connection != null) throw new IllegalStateException();
 8 
 9     this.connection = connection;
10     connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
11   }

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

 1  public static final class StreamAllocationReference extends WeakReference<StreamAllocation> {
 2     /**
 3      * Captures the stack trace at the time the Call is executed or enqueued. This is helpful for
 4      * identifying the origin of connection leaks.
 5      */
 6     public final Object callStackTrace;
 7 
 8     StreamAllocationReference(StreamAllocation referent, Object callStackTrace) {
 9       super(referent);
10       this.callStackTrace = callStackTrace;
11     }
12   }

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

 1 public void streamFinished(boolean noNewStreams, HttpCodec codec) {
 2     Socket socket;
 3     synchronized (connectionPool) {
 4       if (codec == null || codec != this.codec) {
 5         throw new IllegalStateException("expected " + this.codec + " but was " + codec);
 6       }
 7       if (!noNewStreams) {
 8         connection.successCount++;
 9       }
10       socket = deallocate(noNewStreams, false, true);
11     }
12     closeQuietly(socket);
13   }
14 
15 /**
16    * Releases resources held by this allocation. If sufficient resources are allocated, the
17    * connection will be detached or closed. Callers must be synchronized on the connection pool.
18    *
19    * <p>Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion
20    * of the synchronized block. (We don't do I/O while synchronized on the connection pool.)
21    */
22   private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
23     assert (Thread.holdsLock(connectionPool));
24 
25     if (streamFinished) {
26       this.codec = null;
27     }
28     if (released) {
29       this.released = true;
30     }
31     Socket socket = null;
32     if (connection != null) {
33       if (noNewStreams) {
34         connection.noNewStreams = true;
35       }
36       if (this.codec == null && (this.released || connection.noNewStreams)) {
37         release(connection);
38         if (connection.allocations.isEmpty()) {
39           connection.idleAtNanos = System.nanoTime();
40           if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
41             socket = connection.socket();
42           }
43         }
44         connection = null;
45       }
46     }
47     return socket;
48   }
49 
50   /** Remove this allocation from the connection's list of allocations. */
51   private void release(RealConnection connection) {
52     for (int i = 0, size = connection.allocations.size(); i < size; i++) {
53       Reference<StreamAllocation> reference = connection.allocations.get(i);
54       if (reference.get() == this) {
55         connection.allocations.remove(i);
56         return;
57       }
58     }
59     throw new IllegalStateException();
60   }

其中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()这个是无参的方法。

1   public void release() {
2     Socket socket;
3     synchronized (connectionPool) {
4       socket = deallocate(false, true, false);
5     }
6     closeQuietly(socket);
7   }

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

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

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

 1 public void cancel() {
 2     HttpCodec codecToCancel;
 3     RealConnection connectionToCancel;
 4     synchronized (connectionPool) {
 5       canceled = true;
 6       codecToCancel = codec;
 7       connectionToCancel = connection;
 8     }
 9     if (codecToCancel != null) {
10       codecToCancel.cancel();
11     } else if (connectionToCancel != null) {
12       connectionToCancel.cancel();
13     }
14   }

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

 1 public void streamFailed(IOException e) {
 2     Socket socket;
 3     boolean noNewStreams = false;
 4 
 5     synchronized (connectionPool) {
 6       if (e instanceof StreamResetException) {
 7         StreamResetException streamResetException = (StreamResetException) e;
 8         if (streamResetException.errorCode == ErrorCode.REFUSED_STREAM) {
 9           refusedStreamCount++;
10         }
11         // On HTTP/2 stream errors, retry REFUSED_STREAM errors once on the same connection. All
12         // other errors must be retried on a new connection.
13         if (streamResetException.errorCode != ErrorCode.REFUSED_STREAM || refusedStreamCount > 1) {
14           noNewStreams = true;
15           route = null;
16         }
17       } else if (connection != null
18           && (!connection.isMultiplexed() || e instanceof ConnectionShutdownException)) {
19         noNewStreams = true;
20 
21         // If this route hasn't completed a call, avoid it for new connections.
22         if (connection.successCount == 0) {
23           if (route != null && e != null) {
24             routeSelector.connectFailed(route, e);
25           }
26           route = null;
27         }
28       }
29       socket = deallocate(noNewStreams, false, true);
30     }
31 
32     closeQuietly(socket);
33   }

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

 

 

posted @ 2022-12-12 21:32  Boblim  阅读(477)  评论(0编辑  收藏  举报