文章目录
写到前面
本文档基于OkHttp 4.x
这个拦截器是干什么的
简单讲,这个拦截器是为了建立连接的,连接优先来自连接池里面的可用连接,如果连接池里面没有,那么就新建一个连接。
我们前面有提到,RealCall中实际负责发起连接和数据传输的是Exchange,但是到现在为止,Exchange还没有初始化,所以我们要完成Exchange的初始化。
但是层层发现,Exchange的初始化,主要关键在于拿到RealConnection
核心逻辑
简单走读一下代码,就会发现核心的逻辑在ExchangeFinder.findConnect中,
这个类里面,主要用来找到RealConnection
这个类的主要逻辑可以简化成下面这张图
核心代码走读一下?
1. 首先校验当前RealCall中的RealConnection是否可用
val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
if (callConnection != null) {
//正常情况下, 此时的call.connection应该是null, 但是可能这个地方有重复调用的情况, 所以需要先走到里面进行一些处理
var toClose: Socket? = null
synchronized(callConnection) {
//如果不可用,就toClose
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
}
// If the call's connection wasn't released, reuse it. We don't call connectionAcquired() here
// because we already acquired it.
if (call.connection != null) {
check(toClose == null)
return callConnection
}
// The call's connection was released.
toClose?.closeQuietly()
eventListener.connectionReleased(call, callConnection)
}
这里我们要注意下noNewExchanges这个类成员变量,后面我们还会经常看到他,可以说判断RealConnection是否可用主要就是通过这个值来进行判断的
2. 去连接池里面找连接
// Attempt to get a connection from the pool.
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
fun callAcquirePooledConnection(
address: Address,
call: RealCall,
routes: List<Route>?,
requireMultiplexed: Boolean
): Boolean {
for (connection in connections) {
synchronized(connection) {
if (requireMultiplexed && !connection.isMultiplexed) return@synchronized
//判断这个链接是否合格
if (!connection.isEligible(address, routes)) return@synchronized
//请求到了一个connection, 把他丢给realCall
call.acquireConnectionNoEvents(connection)
return true
}
}
return false
}
这里可以看到,主要就是一个循环遍历链接是否“合格”主要的逻辑在connection.isEligible中, 可以看下下面代码, 主要逻辑就是先根据noNewExchanges来判断下这个连接是否已经失效,接在判断下host,ca证书等一大堆乱七八糟的东西是否能用于当前的网络请求。
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
assertThreadHoldsLock()
// If this connection is not accepting new exchanges, we're done.
// 如果判断一个realConnection是否可以用,主要还是靠noNewExchange来的
if (calls.size >= allocationLimit || noNewExchanges) return false
// If the non-host fields of the address don't overlap, we're done.
if (!this.route.address.equalsNonHost(address)) return false
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url.host == this.route().address.url.host) {
//看来检验一个RealConnection可不可用, 主要还是看url是否一样
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.
if (routes == null || !routeMatchesAny(routes)) return false
// 3. This connection's server certificate's must cover the new host.
if (address.hostnameVerifier !== OkHostnameVerifier) 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) {
return false
}
return true // The caller's address can be carried by this connection.
}
3. 如果连接池没有找到合适的,那就要新建了,新建之前,我们要首先找到一个Route
别问我,Route是干啥用的,暂时我也没有看懂,这里存疑,只贴代码
// Nothing in the pool. Figure out what route we'll try next.
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
// Use a route from a preceding coalesced connection.
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
// Use a route from an existing route selection.
routes = null
route = routeSelection!!.next()
} else {
// Compute a new route selection. This is a blocking operation!
var localRouteSelector = routeSelector
if (localRouteSelector == null) {
localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
this.routeSelector = localRouteSelector
}
val localRouteSelection = localRouteSelector.next()
routeSelection = localRouteSelection
routes = localRouteSelection.routes
if (call.isCanceled()) throw IOException("Canceled")
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. We have a better chance of matching thanks to connection coalescing.
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
route = localRouteSelection.next()
}
4. 接着新建RealConnection,并建立连接
对于https的,要建立tls连接。具体看下
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
call.client.routeDatabase.connected(newConnection.route())
fun connect(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
call: Call,
eventListener: EventListener
) {
...
while (true) {
try {
//看是否
if (route.requiresTunnel()) {
// 这里可能是ssl的, 对于https链接有用
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 (e: IOException) {
}
...
}
5. 将新连接放到连接池里面
synchronized(newConnection) {
//把新的连接放到连接池中
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
好了 基本的逻辑就这样!