终于来到OkHttp的网络链接模块,这块内容是OkHttp的核心内容。咱们知道Http的链接须要进行3此握手,断开须要4次挥手。而链接的每一次握手,都须要进行Socket链接、释放,这是一个很是麻烦并且耗时耗力的过程。那么链接的服用就显得尤其重要了,同个地址的链接,若是在用完后不断开,保持链接,在下次的请求中便能重复使用这个链接,节省了链接的时间。这对于大部分时间须要重复频繁访问同一个服务器地址的移动端网络来讲更加不可或缺。java
在本篇文章中,咱们将以ConnectIntercepter为起点,跟随网络链接获取的过程,深刻探究其中涉及到的:链接查找、链接复用,网络链接的创建(三次握手、Http2协议等的处理)。面对这复杂的过程,咱们先整体的走一遍链接获取过程,而后在后续介绍 RealConnection.java 和 ConnectionPool.java 来更深刻的理解链接的创建和缓存查找等逻辑。除此以外,咱们还须要先看一下另外一个类:Transmitter.java ,它将在connect的过程当中起到重要的地位。数据库
RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
Transmitter.java
public final class Transmitter {
private final OkHttpClient client;
//重点:链接池
private final RealConnectionPool connectionPool;
//这次请求
private final Call call;
private Request request;
//重点:链接查找器,它将承当主要的链接查找工做。
private ExchangeFinder exchangeFinder;
//Connecttion的实现类,表明着和服务器的链接。
public RealConnection connection;
//重点:负责请求的发送和响应接收
private @Nullable Exchange exchange;
//请求是否已取消
private boolean canceled;
...
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
复制代码
总结:Transmitter是在建立RealCall的时候被建立的,其中须要了OkHttpClient和当前请求Call做为参数。因此咱们知道了,一个请求对应着一个Transmitter。并且,它的成员变量里有ExchangeFinder等类,负责为这个请求查找到一个合适的请求。缓存
这个方法是释放一个链接,该方法在后面的查找链接中会涉及到,咱们在这里先对其进行讲述。bash
Transmitter.java
@Nullable Socket releaseConnectionNoEvents() {
...
int index = -1;
//一个链接,能够有多个transmitter,也就是用于多个请求。因此在这里须要
//找到本身的那一个。
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;
released.transmitters.remove(index);
this.connection = null;
//若是这个请求释放了这个链接后,这个链接没有被用于其余请求
//调用链接池,使这个链接变为一个空闲链接。
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
//详见【5.4】
if (connectionPool.connectionBecameIdle(released)) {
//没人在用了,把Socket返回回去。
return released.socket();
}
}
//还有其余请求在用,就不返回socket回去。
return null;
}
复制代码
总结:这是一个请求关闭一个链接的过程。服务器
RetryAndFollowUpInterceptor.java
@Override public Response intercept(Chain chain) throws IOException {
...
Transmitter transmitter = realChain.transmitter();
...
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
...
}
}
复制代码
从上面能够看到,在执行第一个默认拦截器的逻辑的时候,调用transmitter.prepareToConnect()方法。咱们接下去看一下这个方法作了上面准备工做。cookie
Transmitter.java
public void prepareToConnect(Request request) {
if (this.request != null) {
//若是这个Transmitter已经有了一个请求了
//而且他们的url所指向的地址都是同一个,那么这个链接能够复用,直接返回。
if (sameConnection(this.request.url(), request.url())) return;
//若是上个请求的信息交换器不为空,表明此次request尚未结束
//那么抛出错误,该Transmitter不能给新的request用。
if (exchange != null) throw new IllegalStateException();
//释放上次的链接。
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
//第一次进来时,直接来到这里。
this.request = request;
//给本身建立一个链接查找器,注意这里的CreateAddress(),它将返回一个Adrees对象,表明着远方服务器的一个地址。
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
复制代码
总结:其实这个方法,重点就是为链接做准备。可是主要目的仍是找到能够复用的链接。它的逻辑以下:网络
Transmitter.java
void acquireConnectionNoEvents(RealConnection connection) {
...
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
复制代码
总结: 这个方法是表明Transmitter得到了一个可用的链接了。那么它作的工做是将这个链接保存起来。而后将本身登记到RealConnection。这个方法后面会有用到,这里先讲解一下。数据结构
有了章节二的预备知识后,咱们能够来看ConnectIntercepter了。不过他只是触发打开链接的按钮,真正链接的查找和链接逻辑在exchangeFinder.java和Exchage.java。无论怎么样,咱们先来看一下开始的地方。并发
【3.1】ConnectIntercepterapp
ConnectIntercepter.java
@Override public Response intercept(Chain chain) throws IOException {
...
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//详见【3.2】
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
复制代码
调用transmitter的newExcahge()方法,获得一个能够与远程地址进行通行的Exchage,而后就丢给下一个拦截器了。顺带说一下,在第一篇《》咱们知道,紧跟着ConnectIntercepter的下一个拦截器是ServerIntercepter,那咱们能够很容易的推理出,它拿到了ConnectIntercepter的excahge后,就进行了数据传输和数据接收。
【3.2】newExchange()
Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
...
//详见3.3:find()
//详见四:ExchangeCodec.java
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()找到一个链接,返回ExchangeCodec。ExchangeCodec是一个接口,它表明着Http请求的加密,和响应的解密。它有2个具体实现:Http1ExchangeCodec和Http2ExchangeCodec,它的详细内容详见【4】。咱们继续看链接的查找。
ExcahgeFinder.java
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 {
//详见【3.5】
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//详见【3.7】
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
复制代码
ExcahgeFinder.java
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
//详见:【3.6】找到链接候选人
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// 若是这个链接是全新的,那么能够直接用
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 在这里须要检查一下这个链接是否健康的
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
//若是不加看,调用RealConnection.noNewExcahge()方法,将此链接丢弃并继续找。
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
复制代码
总结: 该方法顾名思义,就是经过一个while(true)不断的找一个链接候选人,而后检查是否健康可用的,若是不能用就进行标记,丢弃。详细的以下:
接下来就是重中之重了,让咱们来一块儿品味这很香的查找逻辑。
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;
synchronized (connectionPool) {
//1.若是这个请求已经被取消过,那么再次请求抛出错误。
if (transmitter.isCanceled()) throw new IOException("Canceled");
...
//2. 先找到这个链接以前的路由结果
Route previousRoute = retryCurrentRoute()
? transmitter.connection.route()
: null;
//3. 在这里尝试使用一个已经分配好的链接,可是如上文【3.5】看到的
//他会检查它的noNewExchange标志为,若是是true 的话,那么这个链接不但不能用,并且还要复制给toClose,关闭掉。
//详见【2.3】:releaseConnectionNoEvents()
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
//4.若是transmitter的connection历经了上面的的逻辑,没有被置空,说明这个链接可用,赋值给result。
if (transmitter.connection != null) {
result = transmitter.connection;
releasedConnection = null; //这个链接能够用,不能把他释放掉,从新置为空。
}
//5. 若是此时的result仍是为空,说明上面尝试获取一个已经分配好的链接失败
//那么此次尝试重链接池中获取。
if (result == null) {
//详见【5.3】:尝试获取一个链接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
//获取成功
foundPooledConnection = true;
result = transmitter.connection;
} else {
//链接池都获取失败的话,须要进行路由
selectedRoute = previousRoute;
}
}
}
//将刚刚要关闭的链接关闭。
closeQuietly(toClose);
...
//result不空,找到一个可用的链接,直接返回。
if (result != null) {
return result;
}
// 6.进行路由选择
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) {
//7. 因为有新的路由,用路由选择的新的IP集合,再次此时到链接池中找能够复用的链接。
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
//8. 既然路由都没有找到能够用的,那么就建立一个新的RealConnection,
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 9. 若是刚刚第二次在链接池找到了,那么返回这个链接。
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 10. 详见【4.2】说明要用新链接,那么进行TCP+TSL链接
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
//11. 当多个链接链接到同一个主机时,在这里会进行链接合并。这是最后一次尝试
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
//说明链接池中已经有一个可用的链接了,不须要刚刚建立的链接。
pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
//12.详见【5.5】新建立的链接正常使用,将它放入池子中
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
//若是有须要,丢掉刚刚新建立的链接
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
//终于能够返回
return result;
}
复制代码
总结:这是一个查找链接的过程,在查找的时候,综合考虑了自身的链接,路由的结果,链接池的复用,和新建几种方案。具体的以下:
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
if (http2Connection != null) {
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}
复制代码
总结: 根据链接性质不同,生成不一样的数据加解密器。
章节小结:本节从ConnectIntercepter开始,追寻了一个链接如何被得到的过程,它涉及到了新建链接、路由选择,链接池复用等逻辑,最终的产物是Exchange,由它去到下一个拦截器:ServerIntercepter进行网络传输工做。其中Exchange、RealConnectionPool起到了很重要角色,咱们将在下一小节中解析
RealConnection,描述的是一次与远程服务器的链接,因此它须要具有与远程地址进行创建链接,通行的能力。这些能里咱们能够在后续它的成员变量和方法中看出来。照例,咱们来看一下的构造函数和成员变量。
public final class RealConnection extends Http2Connection.Listener implements Connection {
...
private static final int MAX_TUNNEL_ATTEMPTS = 21;
//链接池
public final RealConnectionPool connectionPool;
//路由器
private final Route route;
//这个socket将在connect()方法中被赋值,而且不会再从新赋值。它用于底层的Socket通讯。
private Socket rawSocket;
//表明着应用层的Socket
private Socket socket;
//描述一次完整握手过程的对象。
private Handshake handshake;
//协议枚举类,包括“http/1.0”、“http/3.1”等。
private Protocol protocol;
//表明了一个Http2的Socket链接
private Http2Connection http2Connection;
//与服务器进行数据交互的流操做对象。
private BufferedSource source;
private BufferedSink sink;
//表示connection的一个标志位,被connectionPool管理着,而且一旦为true,将一直为true。表明着这个链接不须要新的Exchage了。
boolean noNewExchanges;
...
/** 这个链接所负载的请求 */
final List<Reference<Transmitter>> transmitters = new ArrayList<>();
...
//构造函数须要链接池和路由器。
public RealConnection(RealConnectionPool connectionPool, Route route) {
this.connectionPool = connectionPool;
this.route = route;
}
复制代码
总结: 一些主要的成员变量已经如上列出注释。接下来从它最重要的方法connect()入手来理解它的做用。
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
//当portacol不等空时,表明链接已经创建,抛出错误。
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
//注意这里的ConnectSpec对象,它表明了Http的Socket通讯的配置,好比它会指定TLS协议版本。
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
//1.对协议配置的一些检查,若是配置不合法将会抛出错误
//HTTP的话,判断是否配置了不容许明文传输或者Android平台规定了不容许明文传输。不知足的抛出错误。
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"));
}
} else {
//若是是Https链接,判断是否配置h2_prior_knowledge。
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 {
//2.检查是否须要隧道模式,若是须要就创建隧道链接。
//若是目标地址是Https协议,可是又经过Http协议代理的话,将会知足断定。
if (route.requiresTunnel()) {
//详见【4.3】
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
//rawSocket为空,表明不能创建隧道链接,退出。
if (rawSocket == null) {
break;
}
} else {
//3.详见【4.4】创建普通的Socket链接
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
//4. 详见【4.5】创建协议。
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
...
}
}
//5. 对隧道链接创建失败的处理
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
}
//若是是Http2协议,获取最大并发流限制
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
复制代码
总结:该方法是Connection处理链接逻辑的地方,主要包括一下几点:
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
EventListener eventListener) throws IOException {
//1. 建立用于隧道链接用的请求。
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
//2. 详见【4.4】和普通链接同样,也须要进行Socket链接
connectSocket(connectTimeout, readTimeout, call, eventListener);
//3. 建立隧道
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;
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
}
}
复制代码
总结: 建立隧道链接,就是在Http代理的代理上创建Https链接。主要的作了以下事情:
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//1. 根据不一样的代理类型来选择不一样的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 {
//2. 采用平台上的链接socket方式
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
...
}
//获得Socket的输出输入流
try {
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);
}
}
}
复制代码
总结: 该方法是与远程服务器地址创建起Socket链接,并得到输入输出流。具体的以下:
socket.connect(address, connectTimeout);
复制代码
在这一步connect事后,socket完成了3次握手创建TCP链接。 3. 得到Socket的输出输入流。
####【4.5】establishProtocol()
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
//1. 判断是否为http请求
if (route.address().sslSocketFactory() == null) {
//2.若是http请求里包涵了“h2_prior_knowledge”协议,表明是一个支持明文的http2请求,因此仍然开启的是http2的链接
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
//3. 创建http2链接
startHttp2(pingIntervalMillis);
return;
}
//4. 不属于以上状况,正常创建http链接
socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}
eventListener.secureConnectStart(call);
//5. 详见【4.6】创建Tls协议
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
//创建http2链接
if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}
复制代码
总结: 该方法根据请求协议,来肯定创建的链接是否须要进一步协议处理。具体的以下:
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// 1. 将刚刚获得的socket经过sslSocketFactory进行包装
//获得SSLSocket对象。
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// 2. 详见【4.7】对sslSocket进行配置协议。
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
//3. 看状况是否进行Tls扩展配置
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
//4. 开始进行三次握手
sslSocket.startHandshake();
SSLSession sslSocketSession = sslSocket.getSession();
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);
//5. 对sslSocket的地址与主机地址进行校验,确保一致可用。
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
if (!peerCertificates.isEmpty()) {
X509Certificate cert = (X509Certificate) 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));
} else {
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified (no certificates)");
}
}
//6. 证书校验
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
//7. 在3中若是配置了进行扩展,那么在这里将会取到协议协商的结果。
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
//8. 将刚才完成握手和协议校验的sslSocket保存起来
//而且得到用于IO传输的source、sink
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) {
...
} finally {
...
}
}
复制代码
总结: 在这个方法里,链接将进行SSL配置,三次握手,证书校验等工做。具体的以下:
ConnectionSpecSelector.java
ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
ConnectionSpec tlsConfiguration = null;
for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
ConnectionSpec connectionSpec = connectionSpecs.get(i);
if (connectionSpec.isCompatible(sslSocket)) {
tlsConfiguration = connectionSpec;
nextModeIndex = i + 1;
break;
}
}
...
Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
return tlsConfiguration;
}
复制代码
总结: 能够看到,对SSLScoket配置,就是遍历connectionSpecs集合,而后挑出适合于这个sslScoket的配置,而后进行要用。具体的以下:
OkHttpClient.java
static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
复制代码
ConnectionSpec.java
void apply(SSLSocket sslSocket, boolean isFallback) {
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
if (specToApply.tlsVersions != null) {
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
}
if (specToApply.cipherSuites != null) {
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
}
}
复制代码
总结: 对这个socket设置tls版本和密码套件
RealConnection.java
boolean isEligible(Address address, @Nullable List<Route> routes) {
// 若是这个链接所承载的请求达到最大,则不能重用
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// 若是不是Host域,看他们地址是否彻底同样。
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// Host域相同,返回能够复用的结果。
if (address.url().host().equals(this.route().address().url().host())) {
return true;
}
//下面是Http2链接复用相关。
....
return true;
}
复制代码
总结: 这个方法在后续的解析中会涉及到,因此先放在这里讲了。主要是用来判断这个链接可不能够复用的。判断条件如注释。
在3.6的findConnetion过程当中,咱们看到了不少次链接池的身影,它对链接的复用也起着绝对重要的位置,若是不仔细的理解它的话,查找链接这块的逻辑就会少一大快。照例,从它的出生、成员变量和构造函数来初步认识它。
OkHttpClient.Builder.java
public Builder() {
...
connectionPool = new ConnectionPool();
}
复制代码
在Builder()里建立默认的链接池。
public final class ConnectionPool {
final RealConnectionPool delegate;
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
}
复制代码
总结: 能够看出到,ConectionPool才用代理模式,实际逻辑交给RealConnection()。5个最大空闲链接,每一个链接可保活5分钟。
public final class RealConnectionPool{
//
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** 每一个地址可保持的最大空闲链接 */
private final int maxIdleConnections;
//链接的保活时间
private final long keepAliveDurationNs;
//链接清理任务
private final Runnable cleanupRunnable = () -> {
while (true) {
//详见【5.6】
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) {
}
}
}
}
};
//链接集合,采用双向链标数据结构
private final Deque<RealConnection> connections = new ArrayDeque<>();
//路由数据库
final RouteDatabase routeDatabase = new RouteDatabase();
//清除任务执行标志
boolean cleanupRunning;
/**
*构造函数
*/
public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
...
}
}
复制代码
总结: 能够看出,这个链接池是用来管理同个地址的链接的。它提供根据地址查找可用链接、清除链接等功能。接下来介绍一下它的几个重要方法。
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
【详见4.9】
if (!connection.isEligible(address, routes)) continue;
【详见2.2】
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
复制代码
总结: 遍历保存的链接,调用RealConnection.isEligible() 来判断这个链接是否符合条件。将这个请求的Transmitter登记到RealConnection。
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
if (connection.noNewExchanges || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
//通知清理任务执行。
notifyAll();
connection limit.
return false;
}
}
复制代码
总结: 将一个链接变为空闲链接。若是此时这个链接不可用的话,将链接从链接集合中移除,并返回true。若是还能够,通知清理任务执行,并返回false。
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
复制代码
总结: 该方法是将一个链接放入链接池中,而后执行清理任务,不过它会被堵塞住,直到【5.4】方法触发。
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
//1. 遍历链接池
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//2. 若是链接还在用,继续遍历
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
//3. 找出最长空闲时间和对于的链接
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//4. 清除空闲最长的链接,并且须要知足以下条件:
//a. 空闲时间大于最大保活时间。
//b. 空闲链接数大于最大空闲链接数
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// 清理不了,返回下次清理须要的时间
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 没有空闲链接,返回keepAliveDuration时间,表明keepAliveDuration后再执行。
return keepAliveDurationNs;
} else {
// 没有空闲或者在用的链接,清理结束。
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// 已经清理了一个,会当即再执行清理任务。
return 0;
}
复制代码
总结: 这是一个清理链接的方法,它作的使其以下:
小篇结:本篇是介绍OkHttp的网络链接创建。开篇先介绍了Trasnmitter这一重要的类,随后从ConnectIntercepter入手,深刻研究了链接Connection的获取逻辑。在获取的过程当中,咱们将到了链接缓存的处理。当获取不到缓存的时候,便会新建一个全新的网络链接,在这个过程当中会进行Http的3次握手等过程。在最后2小节中,分别介绍了在整个过程当中的中心类,被查找对象:RealConnection。和管理缓存Connection的ConnectionPool。最后以一张图来总结这一过程