okhttp学习系列之ConnectInterceptor拦截器

写到前面

本文档基于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)
}

好了 基本的逻辑就这样!

猜你喜欢

转载自blog.csdn.net/weixin_43662090/article/details/114676671
今日推荐