- 1.OkHttp源码解析(一):OKHttp初阶
- 2 OkHttp源码解析(二):OkHttp连接的"前戏"——HTTP的那些事
- 3 OkHttp源码解析(三):OKHttp中阶之线程池和消息队列
- 4 OkHttp源码解析(四):OKHttp中阶之拦截器及调用链
- 5 OkHttp源码解析(五):OKHttp中阶之OKio简介
- 6 OkHttp源码解析(六):OKHttp中阶之缓存基础
- 7 OkHttp源码解析(七):OKHttp中阶之缓存机制
- 8 OkHttp源码解析(八):OKHttp中阶之连接与请求值前奏
- 9 OkHttp源码解析(九):OKHTTP连接中三个"核心"RealConnection、ConnectionPool、StreamAllocation
- 10 OkHttp源码解析(十) OKHTTP中连接与请求
- 11 OkHttp的感谢
本片文章终于讲到OKHTTP中的核心了,复用连接池,本片文章的顺序是
- 1、RealConnection类
- 2、ConnectionPool类
- 3、StreamAllocation类
一、RealConnection
RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过
RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路了。很多朋友这时候会想到,有通信链路了,是不是与意味着在这个类实现的三次握手,你们猜对了,的确是在这个类里面实现的三次握手。在讲握手的之前,看下它的属性和构造函数,对他有个大概的了解。
private final ConnectionPool connectionPool;
private final Route route;
// The fields below are initialized by connect() and never reassigned.
//下面这些字段,通过connect()方法开始初始化,并且绝对不会再次赋值
/** The low-level TCP socket. */
private Socket rawSocket; //底层socket
/**
* The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
* {@link #rawSocket} itself if this connection does not use SSL.
*/
private Socket socket; //应用层socket
//握手
private Handshake handshake;
//协议
private Protocol protocol;
// http2的链接
private Http2Connection http2Connection;
//通过source和sink,大家可以猜到是与服务器交互的输入输出流
private BufferedSource source;
private BufferedSink sink;
// The fields below track connection state and are guarded by connectionPool.
//下面这个字段是 属于表示链接状态的字段,并且有connectPool统一管理
/** If true, no new streams can be created on this connection. Once true this is always true. */
//如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
public boolean noNewStreams;
//成功的次数
public int successCount;
/**
* The maximum number of concurrent streams that can be carried by this connection. If {@code
* allocations.size() < allocationLimit} then new streams can be created on this connection.
*/
//此链接可以承载最大并发流的限制,如果不超过限制,可以随意增加
public int allocationLimit = 1;
通过上面代码,我们可以得出以下结论:
1、里面除了route 字段,部分的字段都是在connect()方法里面赋值的,并且不会再次赋值
2、这里含有source和sink,所以可以以流的形式对服务器进行交互
3、noNewStream可以简单理解为它表示该连接不可用。这个值一旦被设为true,则这个conncetion则不会再创建stream。
4、allocationLimit是分配流的数量上限,一个connection最大只能支持一个1并发
5、allocations是关联StreamAllocation,它用来统计在一个连接上建立了哪些流,通过StreamAllocation的acquire方法和release方法可以将一个allcation对方添加到链表或者移除链表,
其实大家估计已经猜到了connect()里面进行了三次握手,大家也猜对了,那咱们就简单的介绍下connect()方法:
public void connect( int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
// 线路的选择
RouteException routeException = null;
List connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
}
// 连接开始
while (true) {
try {
// 如果要求隧道模式,建立通道连接,通常不是这种
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
// 一般都走这条逻辑了,实际上很简单就是socket的连接
connectSocket(connectTimeout, readTimeout);
}
// https的建立
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
这里的执行过程大体如下;
- 1、检查连接是否已经建立,若已经建立,则抛出异常,否则继续,连接的是否简历由protocol标示,它表示在整个连接建立,及可能的协商过程中选择所有要用到的协议。
- 2、用 集合connnectionspecs构造ConnectionSpecSelector。
- 3、如果请求是不安全的请求,会对请求执行一些额外的限制:
3.1、ConnectionSpec集合必须包含ConnectionSpec.CLEARTEXT。也就是说OkHttp用户可以通过OkHttpClient设置不包含ConnectionSpec.CLEARTEXT的ConnectionSpec集合来禁用所有的明文要求。
3.2、平台本身的安全策略允向相应的主机发送明文请求。对于Android平台而言,这种安全策略主要由系统的组件android.security.NetworkSecurityPolicy执行。平台的这种安全策略不是每个Android版本都有的。Android6.0之后存在这种控制。
(okhttp/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java 里面的isCleartextTrafficPermitted()方法) - 4、根据请求判断是否需要建立隧道连接,如果建立隧道连接则调用
connectTunnel(connectTimeout, readTimeout, writeTimeout); - 5、如果不是隧道连接则调用connectSocket(connectTimeout, readTimeout);建立普通连接。
- 6、通过调用establishProtocol建立协议
- 7、如果是HTTP/2,则设置相关属性。
整个流程已经梳理完,咱们就抠一下具体的细节,首先来看下建立普通连接,因为隧道连接也会用到普通连接的代码:
看下connectSocket()方法
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
// 根据代理类型来选择socket类型,是代理还是直连
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
// 连接socket,之所以这样写是因为支持不同的平台
//里面实际上是 socket.connect(address, connectTimeout);
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// 得到输入/输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
有3种情况需要建立普通连接:
- 无代理
- 明文的HTTP代理
- SOCKS代理
普通连接的建立过程为建立TCP连接,建立TCP连接的过程为:
- 1、创建Socket,非SOCKS代理的情况下,通过SocketFactory创建;在SOCKS代理则传入proxy手动new一个出来。
- 2、为Socket设置超时
- 3、完成特定于平台的连接建立
- 4、创建用于I/O的source和sink
下面我来看下connectSocket()的具体实现,connectSocket()具体实现是AndroidPlatform.java里面的connectSocket()。
关于AndroidPlatform.java请看上一篇文章。
设置了SOCKS代理的情况下,仅有的特别之处在于,是通过传入proxy手动创建Socket。route的socketAddress包含目标HTTP服务器的域名。由此可见SOCKS协议的处理,主要是在Java标准库的java.net.Socket中处理,对于外界而言,就好像是HTTP服务器直接建立连接一样,因此连接时传入的地址都是HTTP服务器的域名。
而对于明文的HTTP代理的情况下,这里灭有任何特殊处理。route的socketAddress包含着代理服务器的IP地址。HTTP代理自身会根据请求及相应的实际内容,建立与HTTP服务器的TCP连接,并转发数据。
这时候我们再来看下建立隧道逻辑:
/**
* Does all the work to build an HTTPS connection over a proxy tunnel. The catch here is that a
* proxy server can issue an auth challenge and then close the connection.
*/
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
throws IOException {
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
int attemptedConnections = 0;
int maxAttempts = 21;
while (true) {
if (++attemptedConnections > maxAttempts) {
throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
}
connectSocket(connectTimeout, readTimeout);
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
if (tunnelRequest == null) break; // Tunnel successfully created.
// The proxy decided to close the connection after an auth challenge. We need to create a new
// connection, but this time with the auth credentials.
closeQuietly(rawSocket);
rawSocket = null;
sink = null;
source = null;
}
}
建立隧道连接的过程又分为几个步骤:
- 创建隧道请求
- 建立Socket连接
- 发送请求建立隧道
隧道请求是一个常规的HTTP请求,只是请求的内容有点特殊。最初创建的隧道请求如:
/**
* Returns a request that creates a TLS tunnel via an HTTP proxy. Everything in the tunnel request
* is sent unencrypted to the proxy server, so tunnels include only the minimum set of headers.
* This avoids sending potentially sensitive data like HTTP cookies to the proxy unencrypted.
*/
private Request createTunnelRequest() {
return new Request.Builder()
.url(route.address().url())
.header("Host", Util.hostHeader(route.address().url(), true))
.header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
.header("User-Agent", Version.userAgent())
.build();
}
一个隧道请求的例子如下:
请求的"Host" header中包含了目标HTTP服务器的域名。建立socket连接的过程这里就不细说了
创建隧道的过程是这样子的:
/**
* To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create
* the proxy connection. This may need to be retried if the proxy requires authorization.
*/
private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,
HttpUrl url) throws IOException {
// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
while (true) {
Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink);
source.timeout().timeout(readTimeout, MILLISECONDS);
sink.timeout().timeout(writeTimeout, MILLISECONDS);
tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
tunnelConnection.finishRequest();
Response response = tunnelConnection.readResponseHeaders(false)
.request(tunnelRequest)
.build();
// The response body from a CONNECT should be empty, but if it is not then we should consume
// it before proceeding.
long contentLength = HttpHeaders.contentLength(response);
if (contentLength == -1L) {
contentLength = 0L;
}
Source body = tunnelConnection.newFixedLengthSource(contentLength);
Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
body.close();
switch (response.code()) {
case HTTP_OK:
// Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
// that happens, then we will have buffered bytes that are needed by the SSLSocket!
// This check is imperfect: it doesn't tell us whether a handshake will succeed, just
// that it will almost certainly fail because the proxy has sent unexpected data.
if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
throw new IOException("TLS tunnel buffered too many bytes!");
}
return null;
case HTTP_PROXY_AUTH:
tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response);
if (tunnelRequest == null) throw new IOException("Failed to authenticate with proxy");
if ("close".equalsIgnoreCase(response.header("Connection"))) {
return tunnelRequest;
}
break;
default:
throw new IOException(
"Unexpected response code for CONNECT: " + response.code());
}
}
}
在前面创建的TCP连接值上,完成代理服务器的HTTP请求/响应交互。请求的内容类似下面这样:
"CONNECT m.taobao.com:443 HTTP/1.1"
这里可能会根据HTTP代理是否需要认证而有多次HTTP请求/响应交互。
总结一下OkHttp3中代理相关的处理;
- 1、没有设置代理的情况下,直接与HTTP服务器建立TCP连接,然后进行HTTP请求/响应的交互。
- 2、设置了SOCKS代理的情况下,创建Socket时,为其传入proxy,连接时还是以HTTP服务器为目标。在标准库的Socket中完成SOCKS协议相关的处理。此时基本上感知不到代理的存在。
- 3、设置了HTTP代理时的HTTP请求,与HTTP代理服务器建立TCP连接。HTTP代理服务器解析HTTP请求/响应的内容,并根据其中的信息来完成数据的转发。也就是说,如果HTTP请求中不包含"Host"header,则有可能在设置了HTTP代理的情况下无法与HTTP服务器建立连接。
- 4、HTTP代理时的HTTPS/HTTP2请求,与HTTP服务器建立通过HTTP代理的隧道连接。HTTP代理不再解析传输的数据,仅仅完成数据转发的功能。此时HTTP代理的功能退化为如同SOCKS代理类似。
- 5、设置了代理类时,HTTP的服务器的域名解析会交给代理服务器执行。其中设置了HTTP代理时,会对HTTP代理的域名做域名解析。
上述流程弄明白后,来看下建立协议
不管是建立隧道连接,还是建立普通连接,都少不了建立协议这一步骤,这一步是建立好TCP连接之后,而在该TCP能被拿来手法数据之前执行的。它主要为了数据的加密传输做一些初始化,比如TCL握手,HTTP/2的协商。
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
//如果不是ssl
if (route.address().sslSocketFactory() == null) {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
return;
}
//如果是sll
connectTls(connectionSpecSelector);
//如果是HTTP2
if (protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.build();
http2Connection.start();
}
}
上面的代码大体上可以归纳为两点
- 1、对于加密的数据传输,创建TLS连接。对于明文传输,则设置protocol和socket。socket直接指向应用层,如HTTP或HTTP/2,交互的Socket。
1.1对于明文传输没有设置HTTP代理的HTTP请求,它是与HTTP服务器之间的TCP socket。
1.2对于加密传输没有设置HTTP代理服务器的HTTP或HTTP2请求,它是与HTTP服务器之间的SSLSocket。
1.3对于加密传输设置了HTTP代理服务器的HTTP或HTTP2请求,它是与HTTP服务器之间经过代理服务器的SSLSocket,一个隧道连接;
1.4对于加密传输设置了SOCKS代理的HTTP或HTTP2请求,它是一条经过了代理服务器的SSLSocket连接。 - 2、对于HTTP/2,通过new 一个Http2Connection.Builder会建立HTTP/2连接 Http2Connection,然后执行http2Connection.start()和服务器建立协议。我们先来看下建立TLS连接的connectTls()方法
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
//在原来的Socket加一层ssl
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// Configure the socket's ciphers, TLS versions, and extensions.
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
// Force handshake. This can throw!
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
}
// Check that the certificate pinner is satisfied by the certificates presented.
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}
TLS连接是对原始TCP连接的一个封装,以及听过TLS握手,及数据手法过程中的加解密等功能。在Java中,用SSLSocket来描述。上面建立的TLS连接的过程大体为:
- 1、用SSLSocketFactory基于原始的TCP Socket,创建一个SSLSocket。
- 2、并配置SSLSocket。
- 3、在前面选择的ConnectionSpec支持TLS扩展参数时,配置TLS扩展参数。
- 4、启动TLS握手
- 5、TLS握手完成之后,获取证书信息。
- 6、对TLS握手过程中传回来的证书进行验证。
- 7、在前面选择的ConnectionSpec支持TLS扩展参数时,获取TLS握手过程中顺便完成的协议协商过程所选择的协议。这个过程主要用于HTTP/2的ALPN扩展。
- 8、OkHttp主要使用Okio来做IO操作,这里会基于前面获取到SSLSocket创建于执行的IO的BufferedSource和BufferedSink等,并保存握手信息以及所选择的协议。
至此连接已经建立连接已经结束了。
这里说一下isHealthy(boolean doExtensiveChecks)方法,入参是一个布尔类,表示是否需要额外的检查。这里主要是检查,判断这个连接是否是健康的连接,即是否可以重用。那我们来看下
/** Returns true if this connection is ready to host new streams. */
public boolean isHealthy(boolean doExtensiveChecks) {
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
if (http2Connection != null) {
return !http2Connection.isShutdown();
}
if (doExtensiveChecks) {
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
if (source.exhausted()) {
return false; // Stream is exhausted; socket is closed.
}
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
// Read timed out; socket is good.
} catch (IOException e) {
return false; // Couldn't read; socket is closed.
}
}
return true;
}
看上述代码可知,同时满足如下条件才是健康的连接,否则返回false
- 1、socket已经关闭
- 2、输入流关闭
- 3、输出流关闭
- 4、如果是HTTP/2连接,则HTTP/2连接也要关闭。
让我们再来看下isEligible(Address, Route)方法,这个方法主要是判断面对给出的addres和route,这个realConnetion是否可以重用。
/**
* Returns true if this connection can carry a stream allocation to {@code address}. If non-null
* {@code route} is the resolved route for a connection.
*/
public boolean isEligible(Address address, Route route) {
// If this connection is not accepting new streams, we're done.
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// At this point we don't have a hostname match. But we still be able to carry the request if
// our connection coalescing requirements are met. See also:
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. This connection must be HTTP/2.
if (http2Connection == null) return false;
// 2. The routes must share an IP address. This requires us to have a DNS address for both
// hosts, which only happens after route planning. We can't coalesce connections that use a
// proxy, since proxies don't tell us the origin server's IP address.
if (route == null) return false;
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
// 3. This connection's server certificate's must cover the new host.
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 4. Certificate pinning must match the host.
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // The caller's address can be carried by this connection.
}
判断逻辑如下:
- 如果连接达到共享上限,则不能重用
- 非host域必须完全一样,如果不一样不能重用
- 如果此时host域也相同,则符合条件,可以被复用
- 如果host不相同,在HTTP/2的域名切片场景下一样可以复用
关于HTTP/2的可以参考下面的文章
https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
最后再来看下newCodec(OkHttpClient, StreamAllocation)方法
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
里面主要是判断是否是HTTP/2,如果是HTTP/2则new一个Http2Codec。如果不是HTTP/2则new一个Http1Codec。
上面提到了connection的跟踪状态由ConncetionPool来管理。
二、ConnectionPool
大家先来看下一个类的注释
/**
* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
* share the same {@link Address} may share a {@link Connection}. This class implements the policy
* of which connections to keep open for future use.
*/
简单的翻译下,如下:
管理http和http/2的链接,以便减少网络请求延迟。同一个address将共享同一个connection。该类实现了复用连接的目标。
然后看下这个类的字段:
/**
* Background threads are used to cleanup expired connections. There will be at most a single
* thread running per connection pool. The thread pool executor permits the pool itself to be
* garbage collected.
*/
//这是一个用于清楚过期链接的线程池,每个线程池最多只能运行一个线程,并且这个线程池允许被垃圾回收
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
//每个address的最大空闲连接数。
private final int maxIdleConnections;
private final long keepAliveDurationNs;
//清理任务
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
//链接的双向队列
private final Deque connections = new ArrayDeque<>();
//路由的数据库
final RouteDatabase routeDatabase = new RouteDatabase();
//清理任务正在执行的标志
boolean cleanupRunning;
来看下它的属性,
- 1、主要就是connections,可见ConnectionPool内部以队列方式存储连接;
- 2、routDatabase是一个黑名单,用来记录不可用的route,但是看代码貌似ConnectionPool并没有使用它。所以此处不做分析。
- 3、剩下的就是和清理有关了,所以executor是清理任务的线程池,cleanupRunning是清理任务的标志,cleanupRunnable是清理任务。
再来看下他的构造函数
/**
* Create a new connection pool with tuning parameters appropriate for a single-user application.
* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
*/
//创建一个适用于单个应用程序的新连接池。
//该连接池的参数将在未来的okhttp中发生改变
//目前最多可容乃5个空闲的连接,存活期是5分钟
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
//保持活着的时间,否则清理将旋转循环
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
通过这个构造器我们知道了这个连接池最多维持5个连接,且每个链接最多活5分钟。并且包含一个线程池包含一个清理任务。
所以maxIdleConnections和keepAliveDurationNs则是清理中淘汰连接的的指标,这里需要说明的是maxIdleConnections是值每个地址上最大的空闲连接数。所以OkHttp只是限制与同一个远程服务器的空闲连接数量,对整体的空闲连接并没有限制。
PS:
这时候说下ConnectionPool的实例化的过程,一个OkHttpClient只包含一个ConnectionPool,其实例化也是在OkHttpClient的过程。这里说一下ConnectionPool各个方法的调用并没有直接对外暴露,而是通过OkHttpClient的Internal接口统一对外暴露。
然后我们来看下他的get和put方法
/**
* Returns a recycled connection to {@code address}, or null if no such connection exists. The
* route is null if the address has not yet been routed.
*/
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
//断言,判断线程是不是被自己锁住了
assert (Thread.holdsLock(this));
// 遍历已有连接集合
for (RealConnection connection : connections) {
//如果connection和需求中的"地址"和"路由"匹配
if (connection.isEligible(address, route)) {
//复用这个连接
streamAllocation.acquire(connection);
//返回这个连接
return connection;
}
}
return null;
}
get() 方法遍历 connections 中的所有 RealConnection 寻找同时满足条件的RealConnection。
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
put方法更为简单,就是异步触发清理任务,然后将连接添加到队列中。那么下面开始重点分析他的清理任务。
private final long keepAliveDurationNs;
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
这个逻辑也很简单,就是调用cleanup方法执行清理,并等待一段时间,持续清理,其中cleanup方法返回的值来来决定而等待的时间长度。那我们继续来看下cleanup函数:
/**
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
* either it has exceeded the keep alive limit or the idle connections limit.
*
* Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
* -1 if no further cleanups are required.
*/
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
//统计空闲连接数量
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
//找出空闲时间最长的连接以及对应的空闲时间
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
//在符合清理条件下,清理空闲时间最长的连接
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
//不符合清理条件,则返回下次需要执行清理的等待时间,也就是此连接即将到期的时间
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
//没有空闲的连接,则隔keepAliveDuration(分钟)之后再次执行
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
//清理结束
cleanupRunning = false;
return -1;
}
}
//关闭socket资源
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
//这里是在清理一个空闲时间最长的连接以后会执行到这里,需要立即再次执行清理
return 0;
}
这里的首先统计空闲连接数量,然后通过for循环查找最长空闲时间的连接以及对应空闲时长,然后判断是否超出最大空闲连接数(maxIdleConnections)或者或者超过最大空闲时间(keepAliveDurationNs),满足其一则清除最长空闲时长的连接。如果不满足清理条件,则返回一个对应等待时间。
这个对应等待的时间又分二种情况:
- 1 有连接则等待下次需要清理的时间去清理:keepAliveDurationNs-longestIdleDurationNs;
- 2 没有空闲的连接,则等下一个周期去清理:keepAliveDurationNs
如果清理完毕返回-1。
综上所述,我们来梳理一下清理任务,清理任务就是异步执行的,遵循两个指标,最大空闲连接数量和最大空闲时长,满足其一则清理空闲时长最大的那个连接,然后循环执行,要么等待一段时间,要么继续清理下一个连接,知道清理所有连接,清理任务才结束,下一次put的时候,如果已经停止的清理任务则会被再次触发
/**
* Prunes any leaked allocations and then returns the number of remaining live allocations on
* {@code connection}. Allocations are leaked if the connection is tracking them but the
* application code has abandoned them. Leak detection is imprecise and relies on garbage
* collection.
*/
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List> references = connection.allocations;
//遍历弱引用列表
for (int i = 0; i < references.size(); ) {
Reference reference = references.get(i);
//若StreamAllocation被使用则接着循环
if (reference.get() != null) {
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
StreamAllocation.StreamAllocationReference streamAllocRef =
(StreamAllocation.StreamAllocationReference) reference;
String message = "A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
//若StreamAllocation未被使用则移除引用,这边注释为泄露
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
//如果列表为空则说明此连接没有被引用了,则返回0,表示此连接是空闲连接
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}
pruneAndGetAllocationCount主要是用来标记泄露连接的。内部通过遍历传入进来的RealConnection的StreamAllocation列表,如果StreamAllocation被使用则接着遍历下一个StreamAllocation。如果StreamAllocation未被使用则从列表中移除,如果列表中为空则说明此连接连接没有引用了,返回0,表示此连接是空闲连接,否则就返回非0表示此连接是活跃连接。
接下来让我看下ConnectionPool的connectionBecameIdle()方法,就是当有连接空闲时,唤起cleanup线程清洗连接池
/**
* Notify this pool that {@code connection} has become idle. Returns true if the connection has
* been removed from the pool and should be closed.
*/
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
//该连接已经不可用
if (connection.noNewStreams || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
//欢迎clean 线程
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
return false;
}
}
connectionBecameIdle标示一个连接处于空闲状态,即没有流任务,那么久需要调用该方法,由ConnectionPool来决定是否需要清理该连接。
再来看下deduplicate()方法
/**
* Replaces the connection held by {@code streamAllocation} with a shared connection if possible.
* This recovers when multiple multiplexed connections are created concurrently.
*/
Socket deduplicate(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, null)
&& connection.isMultiplexed()
&& connection != streamAllocation.connection()) {
return streamAllocation.releaseAndAcquire(connection);
}
}
return null;
}
该方法主要是针对HTTP/2场景下多个多路复用连接清除的场景。如果是当前连接是HTTP/2,那么所有指向该站点的请求都应该基于同一个TCP连接。这个方法比较简单就不详细说了,再说下另外一个方法
/** Close and remove all idle connections in the pool. */
public void evictAll() {
List evictedConnections = new ArrayList<>();
synchronized (this) {
for (Iterator i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (connection.allocations.isEmpty()) {
connection.noNewStreams = true;
evictedConnections.add(connection);
i.remove();
}
}
}
for (RealConnection connection : evictedConnections) {
closeQuietly(connection.socket());
}
}
该方法是删除所有空闲的连接,比较简单,不说了
三、 StreamAllocation
这个类很重要,我们先来看下类的注释
/**
* This class coordinates the relationship between three entities:
*
*
* - Connections: physical socket connections to remote servers. These are
* potentially slow to establish so it is necessary to be able to cancel a connection
* currently being connected.
*
- Streams: logical HTTP request/response pairs that are layered on
* connections. Each connection has its own allocation limit, which defines how many
* concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
* at a time, HTTP/2 typically carry multiple.
*
- Calls: a logical sequence of streams, typically an initial request and
* its follow up requests. We prefer to keep all streams of a single call on the same
* connection for better behavior and locality.
*
*
* Instances of this class act on behalf of the call, using one or more streams over one or more
* connections. This class has APIs to release each of the above resources:
*
*
* - {@link #noNewStreams()} prevents the connection from being used for new streams in the
* future. Use this after a {@code Connection: close} header, or when the connection may be
* inconsistent.
*
- {@link #streamFinished streamFinished()} releases the active stream from this allocation.
* Note that only one stream may be active at a given time, so it is necessary to call
* {@link #streamFinished streamFinished()} before creating a subsequent stream with {@link
* #newStream newStream()}.
*
- {@link #release()} removes the call's hold on the connection. Note that this won't
* immediately free the connection if there is a stream still lingering. That happens when a
* call is complete but its response body has yet to be fully consumed.
*
*
* This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have the
* smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that stream
* but not the other streams sharing its connection. But if the TLS handshake is still in progress
* then canceling may break the entire connection.
*/
在讲解这个类的时候不得不说下背景:
HTTP的版本:
HTTP的版本从最初的1.0版本,到后续的1.1版本,再到后续的google推出的SPDY,后来再推出2.0版本,http协议越来越完善。(ps:okhttp也是根据2.0和1.1/1.0作为区分,实现了两种连接机制)这里要说下http2.0和http1.0,1.1的主要区别,2.0解决了老版本(1.1和1.0)最重要两个问题:连接无法复用和head of line blocking (HOL)问题.2.0使用多路复用的技术,多个stream可以共用一个socket连接,每个tcp连接都是通过一个socket来完成的,socket对应一个host和port,如果有多个stream(也就是多个request)都是连接在一个host和port上,那么它们就可以共同使用同一个socket,这样做的好处就是可以减少TCP的一个三次握手的时间。在OKHttp里面,记录一次连接的是RealConnection,这个负责连接,在这个类里面用socket来连接,用HandShake来处理握手。
在讲解这个类的之前我们先熟悉3个概念:请求、连接、流。我们要明白HTTP通信执行网络"请求"需要在"连接"上建立一个新的"流",我们将StreamAllocation称之流的桥梁,它负责为一次"请求"寻找"连接"并建立"流",从而完成远程通信。所以说StreamAllocation与"请求"、"连接"、"流"都有关。
从注释我们看到。Connection是建立在Socket之上的物流通信信道,而Stream则是代表逻辑的流,至于Call是对一次请求过程的封装。之前也说过一个Call可能会涉及多个流(比如重定向或者auth认证等情况)。所以我们想一下,如果StreamAllocation要想解决上述问题,需要两个步骤,一是寻找连接,二是获取流。所以StreamAllocation里面应该包含一个Stream(上文已经说到了,OKHttp里面的流是HttpCodec);还应该包含连接Connection。如果想找到合适的刘姐,还需要一个连接池ConnectionPool属性。所以应该有一个获取流的方法在StreamAllocation里面是newStream();找到合适的流的方法findConnection();还应该有完成请求任务的之后finish()的方法来关闭流对象,还有终止和取消等方法,以及释放资源的方法。
1、那咱们先就看下他的属性
public final Address address;//地址
private Route route; //路由
private final ConnectionPool connectionPool; //连接池
private final Object callStackTrace; //日志
// State guarded by connectionPool.
private final RouteSelector routeSelector; //路由选择器
private int refusedStreamCount; //拒绝的次数
private RealConnection connection; //连接
private boolean released; //是否已经被释放
private boolean canceled //是否被取消了
看完属性,我们来看下构造函数
public StreamAllocation(ConnectionPool connectionPool, Address address, Object callStackTrace) {
this.connectionPool = connectionPool;
this.address = address;
this.routeSelector = new RouteSelector(address, routeDatabase());
this.callStackTrace = callStackTrace;
}
这时候我们再来看下他的一个比较重要的方法
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//获取一个连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
//实例化HttpCodec,如果是HTTP/2则是Http2Codec否则是Http1Codec
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
这里面两个重要方法
1是通过findHealthyConnection获取一个连接、2是通过resultConnection.newCodec获取流。
我们接着来看findHealthyConnection()方法
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
我们看到里面调用findConnection来获取一个RealConnection,然后通过RealConnection自己的方法isHealthy,去判断是否是健康的连接,如果是健康的连接,则重用,否则就继续查找。那我们继续看下findConnection()方法
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
//获取存在的连接
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
// 如果已经存在的连接满足要求,则使用已存在的连接
return allocatedConnection;
}
//从缓存中去取
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// 线路的选择,多ip的支持
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
//里面是个递归
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// Now that we have an IP address, make another attempt at getting a connection from the pool.
// This could match due to connection coalescing.
//更换路由再次尝试
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) return connection;
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
// 以上都不符合,创建一个连接
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
//连接并握手
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
//更新本地数据库
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// Pool the connection.
//把连接放到连接池中
Internal.instance.put(connectionPool, result);
//如果这个连接是多路复用
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
//调用connectionPool的deduplicate方法去重。
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
//如果是重复的socket则关闭socket,不是则socket为nul,什么也不做
closeQuietly(socket);
//返回整个连接
return result;
}
上面代码大概的逻辑是:
- 1、先找是否有已经存在的连接,如果有已经存在的连接,并且可以使用(!noNewStreams)则直接返回。
- 2、根据已知的address在connectionPool里面找,如果有连接,则返回
- 3、更换路由,更换线路,在connectionPool里面再次查找,如果有则返回。
- 4、如果以上条件都不满足则直接new一个RealConnection出来
- 5、new出来的RealConnection通过acquire关联到connection.allocations上
- 6、做去重判断,如果有重复的socket则关闭
里面涉及到的RealConnection的connect()方法,我们已经在RealConnection里面讲过,这里就不讲了。不过这里说下acquire()方法
/**
* Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
* {@link #release} on the same connection.
*/
public void acquire(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
这里相当于给connection的引用计数器加1
这里说下StreamAllocationReference,StreamAllocationReference其实是弱引用的子类。具体代码如下:
public static final class StreamAllocationReference extends WeakReference {
/**
* Captures the stack trace at the time the Call is executed or enqueued. This is helpful for
* identifying the origin of connection leaks.
*/
public final Object callStackTrace;
StreamAllocationReference(StreamAllocation referent, Object callStackTrace) {
super(referent);
this.callStackTrace = callStackTrace;
}
}
下面来看下他的他的其他方法streamFinished(boolean, HttpCodec)、release(RealConnection)和deallocate(boolean, boolean, boolean)方法。
public void streamFinished(boolean noNewStreams, HttpCodec codec) {
Socket socket;
synchronized (connectionPool) {
if (codec == null || codec != this.codec) {
throw new IllegalStateException("expected " + this.codec + " but was " + codec);
}
if (!noNewStreams) {
connection.successCount++;
}
socket = deallocate(noNewStreams, false, true);
}
closeQuietly(socket);
}
/**
* Releases resources held by this allocation. If sufficient resources are allocated, the
* connection will be detached or closed. Callers must be synchronized on the connection pool.
*
* Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion
* of the synchronized block. (We don't do I/O while synchronized on the connection pool.)
*/
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
assert (Thread.holdsLock(connectionPool));
if (streamFinished) {
this.codec = null;
}
if (released) {
this.released = true;
}
Socket socket = null;
if (connection != null) {
if (noNewStreams) {
connection.noNewStreams = true;
}
if (this.codec == null && (this.released || connection.noNewStreams)) {
release(connection);
if (connection.allocations.isEmpty()) {
connection.idleAtNanos = System.nanoTime();
if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
socket = connection.socket();
}
}
connection = null;
}
}
return socket;
}
/** Remove this allocation from the connection's list of allocations. */
private void release(RealConnection connection) {
for (int i = 0, size = connection.allocations.size(); i < size; i++) {
Reference reference = connection.allocations.get(i);
if (reference.get() == this) {
connection.allocations.remove(i);
return;
}
}
throw new IllegalStateException();
}
其中deallocate(boolean, boolean, boolean)和release(RealConnection)方法都是private,而且均在streamFinished里面调用。
release(RealConnection)方法比较简单,主要是把RealConnection对应的allocations清除掉,把计数器归零。
deallocate(boolean, boolean, boolean)方法也简单,根据传入的三个布尔类型的值进行操作,如果streamFinished为true则代表关闭流,所以要通过连接池connectionPool把这个connection设置空闲连接,如果可以设为空闲连接则返回这个socket。不能则返回null。
streamFinished()主要做了一些异常判断,然后调用deallocate()方法
综上所述:streamFinished(boolean, HttpCodec)主要是关闭流,release(RealConnection)主要是释放connection的引用,deallocate(boolean, boolean, boolean)主要是根据参数做一些设置。
上面说到了release(RealConnection),为了防止大家混淆概念,这里说一下另外一个方法release()这个是无参的方法。
public void release() {
Socket socket;
synchronized (connectionPool) {
socket = deallocate(false, true, false);
}
closeQuietly(socket);
}
注意这个和上面的带有RealConnection的参数release()的区别。
然后说一下noNewStreams()方法,主要是设置防止别人在这个连接上开新的流。
/** Forbid new streams from being created on the connection that hosts this allocation. */
public void noNewStreams() {
Socket socket;
synchronized (connectionPool) {
socket = deallocate(true, false, false);
}
closeQuietly(socket);
}
还有一个方法,平时也是经常有遇到的就是cancel()方法
public void cancel() {
HttpCodec codecToCancel;
RealConnection connectionToCancel;
synchronized (connectionPool) {
canceled = true;
codecToCancel = codec;
connectionToCancel = connection;
}
if (codecToCancel != null) {
codecToCancel.cancel();
} else if (connectionToCancel != null) {
connectionToCancel.cancel();
}
}
其实也比较简单的就是调用RealConnection的Cancel方法。
如果在连接中过程出现异常,会调用streamFailed(IOException)方法
public void streamFailed(IOException e) {
Socket socket;
boolean noNewStreams = false;
synchronized (connectionPool) {
if (e instanceof StreamResetException) {
StreamResetException streamResetException = (StreamResetException) e;
if (streamResetException.errorCode == ErrorCode.REFUSED_STREAM) {
refusedStreamCount++;
}
// On HTTP/2 stream errors, retry REFUSED_STREAM errors once on the same connection. All
// other errors must be retried on a new connection.
if (streamResetException.errorCode != ErrorCode.REFUSED_STREAM || refusedStreamCount > 1) {
noNewStreams = true;
route = null;
}
} else if (connection != null
&& (!connection.isMultiplexed() || e instanceof ConnectionShutdownException)) {
noNewStreams = true;
// If this route hasn't completed a call, avoid it for new connections.
if (connection.successCount == 0) {
if (route != null && e != null) {
routeSelector.connectFailed(route, e);
}
route = null;
}
}
socket = deallocate(noNewStreams, false, true);
}
closeQuietly(socket);
}
根据异常类型来采取不同的应对措施。注释已经比较清楚了,就不细说了。
其他的方法比较简单,我这里就不细说了。