ConnectInterceptor负责网络连接,其本质是复用连接池中socket连接。
开始撸源码:
intercept(拦截)
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//从拦截器链里得到StreamAllocation对象
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//创建连接等一系列的操作
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//这里是获取前一步的connection.
RealConnection connection = streamAllocation.connection();
//把前面创建的连接,传递到下一个拦截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
步骤详细分析
基本步骤就上面展示了,代码看拦截这里很简单,我们理清楚一下几个类的调用关系,来分析一下连接是如何一步步建立的:
- StreamAllocation
- ConnectionPool
- RealConnection
StreamAllocation
首先,StreamAllocation的初始化在第一个拦截器RetryAndFollowUpInterceptor里面,
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
传入了三个参数,一个连接池,一个地址类,一个调用堆栈跟踪相关的。
在StreamAllocation构造函数中,主要是把这个三个参数保存为内部变量,供后面使用,还有一个就是同时创建了一个线路选择器:
this.routeSelector = new RouteSelector(address, routeDatabase());
该构造器里面有两个重要的参数:
1.使用了Okhttp的连接池ConnectionPool
2.通过url创建了一个Address对象。
Okhttp连接池简单说明:
本篇只是对连接池做最简单的说明,内部的实现原理暂时不细讲。在Okhttp内部的连接池实现类为ConnectionPool,该类持有一个ArrayDeque队列作为缓存池,该队列里的元素为RealConnection(通过这个名字应该不难猜出RealConnection是来干嘛的)。该链接池在初始化OkhttpClient对象的时候由OkhttpClient的Builder类创建,并且ConnectionPool提供了put、get、evictAll等操作。但是Okhttp并没有直接对连接池进行获取,插入等操作;而是专门提供了一个叫Internal的抽象类来操作缓冲池:比如向缓冲池里面put一个RealConnection,从缓冲池get一个RealConnection对象,该类里面有一个public且为static的Internal类型的引用:
//抽象类 public abstract class Internal { public static Internal instance; }1234instance的初始化是在OkhttpClient的static语句块完成的:
static { Internal.instance = new Internal() { //省略部分代码 }; }
newStream()方法
主要做了如下工作:
1)从缓冲池ConnectionPool获取一个RealConnection对象,如果缓冲池里面没有就创建一个RealConnection对象并且放入缓冲池中,具体的说是放入ConnectionPool的ArrayDeque队列中。
2)获取RealConnection对象后并调用其connect(打开Socket链接)。
下面具体分析:
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
//1. 获取设置的连接超时时间,读写超时的时间,以及是否进行重连。
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
// 2. 获取健康可用的连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
//3. 通过resultConnection初始化,对请求以及结果 编解码的类(分http 1.1 和http 2.0)。
// 这里主要是初始化,在后面一个拦截器才用到这相关的东西。
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
获取连接
在上面的代码中最重要的,是注释 第二点,获取健康可用的连接findHealthyConnection,那我们继续深入:
findHealthyConnection()
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
// 1. 加了个死循环,一直找可用的连接
while (true) {
// 2. 这里继续去挖掘,寻找连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// 3. 连接池同步获取,上面找到的连接是否是一个新的连接,如果是的话,就直接返回了,就是我们需要找
// 的连接了
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//4. 如果不是一个新的连接,那么通过判断,是否一个可用的连接。
// 里面是通过Socket的一些方法进行判断的,有兴趣的,可以继续研究一下
// 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。
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;
// 1. 同步线程池,来获取里面的连接
synchronized (connectionPool) {
// 2. 异常的处理。做些判断,是否已经释放,是否编解码类为空,是否用户已经取消 if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 3. 尝试用一下现在的连接,判断一下,是否有可用的连接。(使用已存在的连接)
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// 4. 尝试在连接池中获取一个连接,get方法中会直接调用,注意最后一个参数为空
// 里面是一个for循环,在连接池里面,寻找合格的连接
// 而合格的连接会通过,StreamAllocation中的acquire方法,更新connection的值。(从缓存中获取)
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
//5. 判断上面得到的线路,是否空,如果为空的,寻找一个可用的线路
// 对于线路的选,可以深究一下这个RouteSeletor
// 线路的选择,多ip的支持 if (selectedRoute == null) {
selectedRoute = routeSelector.next();// 里面又个神奇的递归
}
RealConnection result;
//6. 以上都不符合,创建一个连接
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// 7. 由于上面我们获取了一个线路,无论是新建的,或者已有的。
// 我们通过这个线路,继续在连接池中寻找是否有可用的连接。
// 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;
// 8. 如果前面这么寻找,都没在连接池中找打可用的连接,那么就新建一个
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
// 9. 这里就是就是连接的操作了,终于找到连接的正主了,这里会调用RealConnection的连接方法,进行连接操作。
// 如果是普通的http请求,会使用Socket进行连接
// 如果是https,会进行相应的握手,建立通道的操作。
// 这里就不对里面的操作进行详细分析了,有兴趣可以在进去看看
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
// 10. 最后就是同步加到 连接池里面了
synchronized (connectionPool) {
// Pool the connection.
Internal.instance.put(connectionPool, result);
// 最后加了一个多路复用的判断,这个是http2才有的
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
建立连接
connect
建立连接是比较重要的一步了。如果是Https还有证书步骤
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
// 线路的选择
RouteException routeException = null;
List<ConnectionSpec> 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();
}
}
}我们进入connectSocket()函数看看,
connectSocket
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
// 根据代理类型处理socket,为true
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));
}如果使用的是https协议,并且配置有真实将会协议升级。
Https协议的建立 connectTls
如果使用的是https协议socket连接完成后还有一步,就是Tls的处理
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// 在原来socket上加一层ssl
// Create the wrapper over the connected socket.
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);
}
}
}如果对https熟悉的话,应该知道,https就是在http的基础上加了一层。
到此链接完成RealConnection实例化完成。