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 }
判断逻辑如下:
- 如果连接达到共享上限,则不能重用
- 非host域必须完全一样,如果不一样不能重用
- 如果此时host域也相同,则符合条件,可以被复用
- 如果host不相同,在HTTP/2的域名切片场景下一样可以复用 关于HTTP/2的可以参考下面的文章 https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
最后再来看下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 }
根据异常类型来采取不同的应对措施。注释已经比较清楚了,就不细说了。 其他的方法比较简单,我这里就不细说了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
2016-12-12 Unicode(UTF-8, UTF-16)令人混淆的概念