了解OkHttp的网络部分,包括Socket的创建、连接,连接池等要点。OkHttp对Socket的流操作使用了Okio进行了封装。
基础概念
HTTP2的多路复用
HTTP/2引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。HTTP/2对同一域名下所有请求都是基于流,也就是说同一域名不管访问多少文件,也只建立一路连接。每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。多路复用,就是在一个TCP连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。多路复用代替了HTTP1.x的序列和阻塞机制(文本格式)
URL
URL是HTTP和网络的最基本的。除了作为一个通用的,网络资源的分散命名机制,它们也规定了如何访问网络资源。
URLs是抽象的:
- 它们规定了调用可能是明文(http)或密文(https),但是没有规定应该使用哪个加密算法。也没有规定如何验证对等的证书(HostnameVerifier)或者哪个证书可被信任(SSLSocketFactory)。
- 它们没有规定是否一个特定的代理服务器可以使用或如何认证代理服务器。
它们也是具体的:每一个URL确定一个特定路径(像/square/okhttp)和查询(像?q=sharks&lang=en)。每个服务器有很多URL。
Address
地址指定 Web 服务器以及连接到该服务器所需的所有静态配置:端口号、HTTPS 设置和首选网络协议(如 HTTP/2 或 SPDY)。共享同一地址的 URL 也可能共享相同的底层 Socket连接。共享连接具有显著的性能优势:更低的延迟、更高的吞吐量(由于TCP 启动速度较慢)和节省电池电量。OkHttp 使用自动重用 HTTP/1.x 连接和多路复用 HTTP/2 和 SPDY 连接的连接池。在 OkHttp 中,地址的某些字段来自URL(方案、主机名、端口),其余字段来自OkHttpClient。
Routes
Routes提供真正连接到一个网络服务器所需的动态信息。这指定了尝试的IP地址(或者进过DNS查询得到的地址)、使用的代理服务器(如果使用了ProxySelector,网络信息的中转站,大部分有缓冲的功能)和使用哪个版本的TLS进行谈判。(对于HTTPS连接)
对于一个地址,可能有多个路由。举个例子,一个网络服务器托管在多个数据中心,那么在DNS中可能会产生多个IP地址。
Connections
当你使用OkHttp请求一个URL时,下面是它所做的:
- 它使用URL和配置的OkHttpClient来创建一个address。这个address规定了如何连接到服务器。
- 它尝试用这个address从连接池中获取一个连接。
- 如果它没有在池中找到一个连接,它会选择一个route来尝试。这通常意味着创建一个DNS请求来获取服务器的IP地址。如果需要,它之后会选择一个TLS版本和代理服务器。
- 如果这是一个新route,它会通过构建一个直接的socket连接或一个TLS隧道(对于HTTP代理上的HTTPS)或一个直接的TLS连接来进行连接。如果需要它会执行TLS握手。
- 它发送HTTP请求然后读取响应。
当连接出现问题时,OkHttp会选择另外一个route进行尝试。这使得OkHttp可以在服务器部分地址无法访问时恢复。它同时对于当连接池过期或尝试的TLS版本不支持时有用。
一旦接收到响应,连接就会返回到池中,这样它可以在之后的请求复用。连接空闲一段时间会从池中移除。
Address的创建
createAddress方法在Transmitter的prepareToConnect()方法中调用,而prepareToConnect()在RetryAndFollowUpInterceptor的intercept()中调用。
Transmitter # createAddress()
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
// 如果是https
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner(); // 固定证书
}
// 初始化Address
// 可以看到Address的构造方法中的一部分参数由URL提供,一部分由OkHttpClient提供
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
构造方法如下,把相关参数保存下来。
public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
@Nullable SSLSocketFactory sslSocketFactory, @Nullable HostnameVerifier hostnameVerifier,
@Nullable CertificatePinner certificatePinner, Authenticator proxyAuthenticator,
@Nullable Proxy proxy, List<Protocol> protocols, List<ConnectionSpec> connectionSpecs,
ProxySelector proxySelector) {
this.url = new HttpUrl.Builder()
.scheme(sslSocketFactory != null ? "https" : "http")
.host(uriHost)
.port(uriPort)
.build();
if (dns == null) throw new NullPointerException("dns == null");
this.dns = dns;
if (socketFactory == null) throw new NullPointerException("socketFactory == null");
this.socketFactory = socketFactory;
if (proxyAuthenticator == null) {
throw new NullPointerException("proxyAuthenticator == null");
}
this.proxyAuthenticator = proxyAuthenticator;
if (protocols == null) throw new NullPointerException("protocols == null");
this.protocols = Util.immutableList(protocols);
if (connectionSpecs == null) throw new NullPointerException("connectionSpecs == null");
this.connectionSpecs = Util.immutableList(connectionSpecs);
if (proxySelector == null) throw new NullPointerException("proxySelector == null");
this.proxySelector = proxySelector;
this.proxy = proxy;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.certificatePinner = certificatePinner;
}
ExchangeFinder的创建
只是初始化一些属性,在ConnectInterceptor中才使用
在Transmitter的prepareToConnect()方法中创建。
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
connectionPool
通过维护连接池,最大限度重用现有连接,减少网络连接的创建开销,以此提升网络请求效率。
在Transmitter的构造方法中传入client.connectionPool()对connectionPool进行赋值
public Transmitter(OkHttpClient client, Call call) {
//...
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
//...
}
client的连接池是在OkHttpClient.Builder中设置的。一般使用OkHttpClient时,都会将其做成单例,那么连接池就是唯一的.
public Builder() {
...
//默认连接池
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
ConnectionPool的构造方法:
/**
* 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.
*/
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
}
默认的连接池的最大空闲连接数为5,最长存活时间为5min。
RealConnectionPool的构造方法:
public RealConnectionPool(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);
}
}
ExchangeFinder
构造方法:
ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
Address address, Call call, EventListener eventListener) {
this.transmitter = transmitter;
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
// 路由选择器
this.routeSelector = new RouteSelector(
address, connectionPool.routeDatabase, call, eventListener);
}
Exchange和Connection的创建
回到ConnectInterceptor # intercept()
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
Transmitter # newExchange()
/** Returns a new exchange to carry a new request and response. */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
if (exchange != null) {
throw new IllegalStateException("cannot make a new request because the previous response "
+ "is still open: please call response.close()");
}
}
// exchangeFinder的find方法中会从连接池中取出一条可用连接,如果没有的话会创建一条连接丢入连接池并进行TCP和TLS握手等准备工作
// ExchangeCodec 是一个真正执行 I/O 的类,定义打包request,解析response的行为
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
ExchangeFinder # find()
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
// 寻找可用连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
ExchangeFinder # 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, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
// 死循环
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
// 判断是新建的连接还是复用的连接,新建的successCount值为0
// 如果是一个全新的连接,跳过额外的健康检查
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.
// 如果候选连接通不过额外的健康检查(Socket、HTTP2连接、Stream 进行了检测),那么继续寻找一个新的候选连接
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
上述过程总结一下:
- 候选连接是一个新连接,直接返回
- 候选连接不是一个全新连接,但是是健康的,也直接返回
- 候选连接不是全新连接,并且不健康,那么继续下一轮的循环。
ExchangeFinder # 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,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
// 对连接池加锁,这里的连接池是在创建ExchangeFinder传入的,最初是在创建OkHttpClient时创建的
// 一般使用OkHttpClient时,都会将其做成单例,那么连接池就是唯一的
// 由于可能存在别的线程从连接池中执行插入以及连接池自身连接的清除工作,所以需要对其进行加锁
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt.
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
// 尝试使用已分配的连接。我们在这里要小心,因为已分配的连接可能已被限制创建新Exchange。
releasedConnection = transmitter.connection;
// 如果当前的连接不能被用来创建新的Exchange,如果此连接没有call则将连接释放并返回对应Socket准备close
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents() // 此方法把transmitter.connection置为null①
: null;
// 当前连接可用
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
// 不存在可用连接
if (result == null) {
// Attempt to get a connection from the pool.
// 尝试从连接池中获取连接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
// 修改当前选择路由为下一路由
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
// 如果当前Connection的路由应当重试,则将选择的路由设置为当前路由
selectedRoute = transmitter.connection.route();
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
// 如果已经找到了已分配的或从连接池中取出的Connection,则直接返回
return result;
}
// If we need a route selection, make one. This is a blocking operation.
// 如果需要进行路由选择,则进行一次路由选择
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing
// 路由选择过后如今有了一组IP地址,我们再次尝试从连接池中获取连接
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// 如果第二次尝试从连接池获取连接仍然失败,则创建新的连接。
// 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.
// 创建连接并立即将其分配给此分配。这使得异步cancel()能够中断我们即将进行的握手。
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 如果第二次尝试从连接池获取连接成功,则将其返回
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 执行TCP+TLS握手,这是个阻塞的过程(针对第二次创建的新连接)②
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
// 最后一次尝试从连接池中获取连接,这种情况只可能在一个host下多个并发连接这种情况下
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
// 如果成功拿到则关闭我们前面创建的连接的Socket,并返回连接池中的连接
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
// 如果失败则在连接池中放入我们刚刚创建的连接,并将其设置为transmitter中的连接
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
寻找连接的步骤:
- 尝试获取 transmitter 中已经存在的连接,也就是当前 Call 之前创建的连接。
- 若获取不到,则尝试从连接池中调用 transmitterAcquirePooledConnection 方法获取连接,传入的 routes 参数为 null
- 若仍获取不到连接,判断是否需要路由选择,如果需要,调用 routeSelector.next 进行路由选择
- 如果进行了路由选择,则再次尝试从连接池中调用 transmitterAcquirePooledConnection 方法获取连接,传入的 routes 为刚刚路由选择后所获取的路由列表
- 若仍然获取不到连接,则调用 RealConnection 的构造函数创建新的连接,并对其执行 TCP + TLS握手。
- TCP + TSL握手之后,会再次尝试从连接池中通过 transmitterAcquirePooledConnection 方法获取连接,这种情况只会出现在一个 Host 对应多个并发连接的情况下(因为 HTTP/2 支持了多路复用,使得多个请求可以并发执行,此时可能有其他使用该 TCP 连接的请求也创建了连接,就不需要重新创建了)。
- 若最后一次从连接池中获取连接获取成功,会释放之前创建的连接的相关资源。
- 若仍获取不到,则将该连接放入连接池,并将其设置为 transmitter 的连接。
Transmitter # releaseConnectionNoEvents()
/**
* Remove the transmitter from the connection's list of allocations. Returns a socket that the
* caller should close.
*/
@Nullable Socket releaseConnectionNoEvents() {
assert (Thread.holdsLock(connectionPool));
int index = -1;
for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
Reference<Transmitter> reference = this.connection.transmitters.get(i);
if (reference.get() == this) {
index = i;
break;
}
}
if (index == -1) throw new IllegalStateException();
RealConnection released = this.connection;
// 在connection的transmitter列表中移除此transmitter
released.transmitters.remove(index);
// 将connection置为空
this.connection = null;
// 如果connection的transmitters列表为空
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
// 如果是将该连接从连接池中移除(返回true),清理连接池(返回false)
if (connectionPool.connectionBecameIdle(released)) {
// 获取该连接的socket
return released.socket();
}
}
return null;
}
RealConnection # connect()
执行TCP+TLS握手,负责将客户端的socket连接到服务端的socket。
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
// 不是HTTPS协议
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"));
}
// 是HTTPS协议
} else {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
throw new RouteException(new UnknownServiceException(
"H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
}
}
while (true) {
try {
// 如果是https请求并且使用了Proxy.Type.HTTP代理
if (route.requiresTunnel()) {
// 建立隧道连接(加密)
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
// 建立普通连接①
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
// 无论是隧道连接还是普通连接,都要建立协议②
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
// 处理异常,清理数据
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
RealConnection # connectSocket()
创建socket以及连接socket
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
// 获取代理和地址
Proxy proxy = route.proxy();
Address address = route.address();
// 根据代理的类型判断是使用socketFactory工厂创建无参的rawSocket还是使用带代理参数的socket构造方法
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
// Platform.get()返回不同平台信息,get()是一个单例,初始化在findPlatform()
// 调用Platform的connectSocket方法进行socket连接
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;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
// 使用Okio封装socket的输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
RealConnection # establishProtocol()
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
// 如果不是https
if (route.address().sslSocketFactory() == null) {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
startHttp2(pingIntervalMillis);
return;
}
socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}
// 如果是https
eventListener.secureConnectStart(call);
// TLS握手
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
// 如果是http2协议
if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}
ConnectionPool
连接池用来维护整个okhttp的网络连接,复用连接。它的方法具体在RealConnectionPool中得到实现。
RealConnectionPool中通过一个双端队列(阻塞队列)来维护当前所有连接。
private final Deque<RealConnection> connections = new ArrayDeque<>();
里面主要的方法有:
- put():加入新的连接
- transmitterAcquirePooledConnection():从连接池中获取连接
- connectionBecameIdle():通知连接池连接空闲
- evictAll:关闭所有连接
RealConnectionPool # put()
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
// 如果清理线程没开启则开启
cleanupRunning = true;
// executor为一个线程池,最多每个连接池只会有一个线程在执行清理任务
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
当第一个连接被添加到连接池时,开启清除线程,主要清除那些连接池中过期的连接,然后将连接添加到connections对象中。
cleanupRunnable
清理回收
private final Runnable cleanupRunnable = () -> {
while (true) {
// 清理的时候会返回下一次清理的时间间隔
long waitNanos = cleanup(System.nanoTime());
// 没有时间间隔,清除任务结束(当前连接池中没有连接)
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
// 会等待,时间过了之后会继续循环清理。
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
RealConnectionPool # cleanup()
清理符合要求的空闲连接或者返回清理它的等待时间。(gc标记清除算法?)
/**
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
* either it has exceeded the keep alive limit or the idle connections limit.
*
* <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
* -1 if no further cleanups are required.
*/
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
// 查找要逐出的连接,或者查找下一次逐出的等待时间。
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
// 如果这个连接正在使用中,继续寻找①
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
// 空闲连接+1
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.
return keepAliveDurationNs;
} else {
// 说明没有连接,返回-1,关闭清理线程
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
// 对应移除连接情况
closeQuietly(longestIdleConnection.socket());
// 立即清除
// Cleanup again immediately.
return 0;
}
如果连接还在使用,则继续下一次遍历,否则空闲数+1,遍历结束找到最大空闲时间的空闲连接,根据不同情况返回不同结果。
RealConnectionPool # pruneAndGetAllocationCount()
返回connection上transmitter可用数量。利用引用计数法以表明此连接是否是空闲连接。
/**
* Prunes any leaked transmitters and then returns the number of remaining live transmitters on
* {@code connection}. Transmitters 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) {
// 得到关联在connection上的transmitter的弱引用集合
List<Reference<Transmitter>> references = connection.transmitters;
for (int i = 0; i < references.size(); ) {
Reference<Transmitter> reference = references.get(i);
// 如果transmitter不为null,就进行下次遍历
if (reference.get() != null) {
i++;
continue;
}
// 如果所有transmitter都不为null,就不会进行下面操作
// We've discovered a leaked transmitter. This is an application bug.
// 如果此transmitter为null
TransmitterReference transmitterRef = (TransmitterReference) reference;
String message = "A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?";
Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);
// 从集合中删除这个transmitter
references.remove(i);
connection.noNewExchanges = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
// 如果connection上的transmitter都为null,返回数量为0
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}
RealConnectionPool # transmitterAcquirePooledConnection()
从连接池中获取连接。
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
// 当需要进行多路复用而且当前连接不是http2连接,进行下次遍历
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;
// 前两者都不满足的话,则把此连接设置到transmitter,也将它加进connection的transmitter集合 返回true
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
RealConnection # isEligible()
是否该连接可以给对应的address分配stream
/**
* 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.
*/
// 如果该连接可以给对应的address分配stream,则返回true
boolean isEligible(Address address, @Nullable List<Route> routes) {
// If this connection is not accepting new exchanges, we're done.
// 如果这个连接已创建流的数量超过允许承载的最大数量 或者 它不接受新流
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// If the non-host fields of the address don't overlap, we're done.
// 非host域如果不一样(address中的信息)
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// If the host exactly matches, we're done: this connection can carry the address.
// 如果host也相同,判断到这里都一致就可以返回true
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.
// 连接是http2
if (http2Connection == null) return false;
// 2. The routes must share an IP address.
// 路由共享一个ip地址,这要求我们为两个主机都有一个DNS地址
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. This connection's server certificate's must cover the new host.
// 这个连接的服务器证书必须覆盖新主机
if (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.
}
Transmitter # acquireConnectionNoEvents()
把连接设置到transmitter,也将它加进connection的transmitter集合
void acquireConnectionNoEvents(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
RealConnectionPool # connectionBecameIdle()
如果是将该连接从连接池中移除(返回true),清理连接池(返回false)
/**
* 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));
// 如果该连接不支持用于创建新 Exchange,或不允许有空闲连接,则会直接将该连接移除
if (connection.noNewExchanges || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
// 通过 notifyAll 方法唤醒阻塞的清理线程,尝试对空闲连接进行清理
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
return false;
}
}
获得连接后,在CallServerInterceptor中发送请求头、请求体,获得响应头、响应体。