OkHttp源码解析 (三)——代理和路由

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHttp源码解析 (三)——代理和路由相关的知识,希望对你有一定的参考价值。

参考技术A

初看OkHttp源码,由于对Address、Route、Proxy、ProxySelector、RouteSelector等理解不够,读源码非常吃力,看了几遍依然对于寻找复用连接、创建连接、连接服务器、连接代理服务器、创建隧道连接等逻辑似懂非懂,本篇决定梳理一遍相关的概念及基本原理。

● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY

一个http请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,http请求用80;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后,发送http报文数据。

一个https请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,https请求用443;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后进行TLS握手,可通过java标准款提供的SSLSocket完成;
5、握手成功后,发送https报文数据。

1、分类
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5

2、HTTP代理分类及说明
普通代理
HTTP/1.1 协议的第一部分。其代理过程为:
● client 请求 proxy
● proxy 解析请求获取 origin server 地址
● proxy 向 origin server 转发请求
● proxy 接收 origin server 的响应
● proxy 向 client 转发响应
其中proxy获取目的服务器地址的标准方法是解析 request line 里的 request-URL。因为proxy需要解析报文,因此普通代理无法适用于https,因为报文都是加密的。

隧道代理
通过 Web 代理服务器用隧道方式传输基于 TCP 的协议。
请求包括两个阶段,一是连接(隧道)建立阶段,二是数据通信(请求响应)阶段,数据通信是基于 TCP packet ,代理服务器不会对请求及响应的报文作任何的处理,都是原封不动的转发,因此可以代理 HTTPS请求和响应。
代理过程为:
● client 向 proxy 发送 CONNET 请求(包含了 origin server 的地址)
● proxy 与 origin server 建立 TCP 连接
● proxy 向 client 发送响应
● client 向 proxy 发送请求,proxy 原封不动向 origin server 转发请求,请求数据不做任何封装,为原生 TCP packet.

3、SOCKS代理分类及说明
● SOCKS4:只支持TCP协议(即传输控制协议)
● SOCKS5: 既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反过来却不行,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。

有了上面的基础知识,下面分析结合源码分析OkHttp路由相关的逻辑。OkHttp用Address来描述与目标服务器建立连接的配置信息,但请求输入的可能是域名,一个域名可能对于多个ip,真正建立连接是其中一个ip,另外,如果设置了代理,客户端是与代理服务器建立直接连接,而不是目标服务器,代理又可能是域名,可能对应多个ip。因此,这里用Route来描述最终选择的路由,即客户端与哪个ip建立连接,是代理还是直连。下面对比下Address及Route的属性,及路由选择器RouteSelector。

描述与目标服务器建立连接所需要的配置信息,包括目标主机名、端口、dns,SocketFactory,如果是https请求,包括TLS相关的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理服务器信息Proxy 、ProxySelector 。

Route提供了真正连接服务器所需要的动态信息,明确需要连接的服务器IP地址及代理服务器,一个Address可能会有很多个路由Route供选择(一个DNS对应对个IP)。

Address和Route都是数据对象,没有提供操作方法,OkHttp另外定义了RouteSelector来完成选择的路由的操作。

1、读取代理配置信息:resetNextProxy()

读取代理配置:
● 如果有指定代理(不读取系统配置,在OkHttpClient实例中指定),则只用1个该指定代理;
● 如果没有指定,则读取系统配置的,可能有多个。

2、获取需要尝试的socket地址(目标服务器或者代理服务器):resetNextInetSocketAddress()

结合Address的host和代理,解析要尝试的套接字地址(ip+端口)列表:
● 直连或者SOCK代理, 则用目标服务器的主机名和端口,如果是HTTP代理,则用代理服务器的主机名和端口;
● 如果是SOCK代理,根据目标服务器主机名和端口号创建未解析的套接字地址,列表只有1个地址;
● 如果是直连或HTTP代理,先DNS解析,得到InetAddress列表(没有端口),再创建InetSocketAddress列表(带上端口),InetSocketAddress与InetAddress的区别是前者带端口信息。

3、获取路由列表:next()

选择路由的流程解析:
● 遍历每个代理对象,可能多个,直连的代理对象为Proxy.DIRECT(实际是没有中间代理的);
● 对每个代理获取套接字地址列表;
● 遍历地址列表,创建Route,判断Route如果在路由黑名单中,则添加到失败路由列表,不在黑名单中则添加到待返回的Route列表;
● 如果最后待返回的Route列表为空,即可能所有路由都在黑名单中,实在没有新路由了,则将失败的路由集合返回;
● 传入Route列表创建Selection对象,对象比较简单,就是一个目标路由集合,及读取方法。

为了避免不必要的尝试,OkHttp会把连接失败的路由加入到黑名单中,由RouteDatabase管理,该类比较简单,就是一个失败路由集合。

1、创建Address
Address的创建在RetryAndFollowUpInteceptor里,每次请求会声明一个新的Address及StreamAllocation对象,而StreamAllocation使用Address创建RouteSelector对象,在连接时RouteSelector确定请求的路由。

每个Requst都会构造一个Address对象,构造好了Address对象只是有了与服务器连接的配置信息,但没有确定最终服务器的ip,也没有确定连接的路由。

2、创建RouteSelector
在StreamAllocation声明的同时会声明路由选择器RouteSelector,为一次请求寻找路由。

3、选择可用的路由Route

下面在测试过程跟踪实例对象来理解,分别测试直连和HTTP代理HTTP2请求路由的选择过程:
● 直连请求流程
● HTTP代理HTTPS流程
请求url: https://www.jianshu.com/p/63ba15d8877a

1、构造address对象

2、读取代理配置:resetNextProxy

3、解析目标服务器套接字地址:resetNextInetSocketAddress

4、选择Route创建RealConnection

5、确定协议

测试方法:
● 在PC端打开Charles,设置端口,如何设置代理,网上有教程,比较简单;
● 手机打开WIFI,选择连接的WIFI修改网络,在高级选项中设置中指定了代理服务器,ip为PC的ip,端口是Charles刚设置的端口;
● OkHttpClient不指定代理,发起请求。

1、构造address对象

2、读取代理配置:resetNextProxy

3、解析目标服务器套接字地址:resetNextInetSocketAddress

4、选择Route创建RealConnection

5、创建隧道
由于是代理https请求,需要用到隧道代理。

从图可以看出,建立隧道其实是发送CONNECT请求,header包括字段Proxy-Connection,目标主机名,请求内容类似:

6、确定协议,SSL握手

1、代理可分为HTTP代理和SOCK代理;
2、HTTP代理又分为普通代理和隧道代理;普通代理适合明文传输,即http请求;隧道代理仅转发TCP包,适合加密传输,即https/http2;
3、SOCK代理又分为SOCK4和SOCK5,区别是后者支持UDP传输,适合代理聊天工具如QQ;
4、没有设置代理(OkHttpClient没有指定同时系统也没有设置),客户端直接与目标服务器建立TCP连接;
5、设置了代理,代理http请求时,客户端与代理服务器建立TCP连接,如果代理服务器是域名,则解释代理服务器域名,而目标服务器的域名由代理服务器解析;
6、设置了代理,代理https/http2请求时,客户端与代理服务器建立TCP连接,发送CONNECT请求与代理服务器建立隧道,并进行SSL握手,代理服务器不解析数据,仅转发TCP数据包。

如何正确使用 HTTP proxy
OkHttp3中的代理与路由
HTTP 代理原理及实现(一)

ConnectInterceptor 解析——OkHttp 源码详解

前言

框架和流程——OkHttp 源码详解(一)
ConnectInterceptor 解析——OkHttp 源码详解(二)

OkHttp 主要的逻辑实现都在拦截器,拦截器中的重点,就是与服务端建立连接,这个过程包括了,dns,代理选择,tls连接,socket连接等,下面我们就从源码一步步分析

源码分析

下面从拦截器ConnectInterceptor 开始分析:

代码一

代码一:
/**
 * Opens a connection to the target server and proceeds to the next interceptor. The network might
 * be used for the returned response, or to validate a cached response with a conditional GET.
 */
object ConnectInterceptor : Interceptor 
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response 
    //看过上篇文章的,应该很熟悉了,每一个Interceptor 都会被包裹为RealInterceptorChain,注意,这里的chain,是下一个Interceptor
    val realChain = chain as RealInterceptorChain
    //这里的call,就是RealCall,下面对initExchange 展开进行描述,见代码二
    val exchange = realChain.call.initExchange(chain)
    //使用参数,对下一个Interceptor 进行拷贝
    val connectedChain = realChain.copy(exchange = exchange)
    //执行下一个Interceptor
    return connectedChain.proceed(realChain.request)
  

代码二

代码二:
在 RealCall.kt 中
  /** Finds a new or pooled connection to carry a forthcoming request and response. */
  internal fun initExchange(chain: RealInterceptorChain): Exchange 
    synchronized(this) 
      check(expectMoreExchanges)  "released" 
      check(!responseBodyOpen)
      check(!requestBodyOpen)
    
	//exchangeFinder 对象是在RetryAndFollowUpInterceptor中创建的,见代码三
    val exchangeFinder = this.exchangeFinder!!
    //find 创建连接或者在连接池里找到一个可用链接
    // codec ExchangeCodec 接口,它是一个socket 链接,用来传输request和response,
    //有两种实现:Http1ExchangeCodec ,Http2ExchangeCodec 
    val codec = exchangeFinder.find(client, chain)
    //创建了一个Exchange,它负责单个http 请求和响应对,真正的实现是在codec 中(实际的IO操作)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    //保存这个Exchange 对象,在RealCall对象的变量中
    this.interceptorScopedExchange = result
    this.exchange = result
    synchronized(this) 
      this.requestBodyOpen = true
      this.responseBodyOpen = true
    

    if (canceled) throw IOException("Canceled")
    return result
  

因为本篇文章,主要是研究,链接建立的过程,所以关于Exchange传输数据的就先不展开,下面来看一个 ExchangeFinder是在哪里 创建的

代码三

在RetryAndFollowUpInterceptor 的intercept 中,调用了下面的函数,为RealCall 对象,创建了一个ExchangeFinder

代码三:
在 RealCall.kt 中

  fun enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) 
    check(interceptorScopedExchange == null)

    synchronized(this) 
      check(!responseBodyOpen) 
        "cannot make a new request because the previous response is still open: " +
            "please call response.close()"
      
      check(!requestBodyOpen)
    

    if (newExchangeFinder) 
      //创建一个ExchangeFinder,在它里面实现了Dns,代理选择,路由选择,tls连接,socket连接
      //具体每个功能的实现,也是有不同的类去完成,下面会一一详解RetryAndFollowUpInterceptor
      this.exchangeFinder = ExchangeFinder(
          connectionPool,
          createAddress(request.url),
          this,
          eventListener
      )
    
  

下面就看看代码二中的exchangeFinder.find(client, chain) 函数,看看链接建立的详细过程

代码四

代码四:
在ExchangeFinder.kt 中
  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec 
    try 
      // 下面到findHealthyConnection函数中一看究竟
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      //resultConnection 是 RealConnection 类型,它负责建立socket链接 ,建立tls链接,建立Tunnel(通过http代理,访问https 服务器)等操作
      //面对http1和http2,又有各自不同的传输方式,所以使用newCodec() 抽象为ExchangeCodec,http1和http2 实现不同的接口功能(策略者模式)
      return resultConnection.newCodec(client, chain)
     catch (e: RouteException) 
      trackFailure(e.lastConnectException)
      throw e
     catch (e: IOException) 
      trackFailure(e)
      throw RouteException(e)
    
  

下面进入findHealthyConnection 函数中

代码五


代码五:
在ExchangeFinder.kt 中
  /**
   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
   * until a healthy connection is found.
   */
  @Throws(IOException::class)
  private fun findHealthyConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    doExtensiveHealthChecks: Boolean
  ): RealConnection 
    //这里是一个死循环,退出循环的两种情况:1、得到一个健康的连接 2、抛出一个异常
    while (true) 
     //得到一个连接,新建或者在连接池中,见代码六
      val candidate = findConnection(
          connectTimeout = connectTimeout,
          readTimeout = readTimeout,
          writeTimeout = writeTimeout,
          pingIntervalMillis = pingIntervalMillis,
          connectionRetryEnabled = connectionRetryEnabled
      )

      // Confirm that the connection is good.
      if (candidate.isHealthy(doExtensiveHealthChecks)) 
        return candidate
      
      // 如果连接,不可用,就从连接池中删除
      candidate.noNewExchanges()

      // Make sure we have some routes left to try. One example where we may exhaust all the routes
      // would happen if we made a new connection and it immediately is detected as unhealthy.
      if (nextRouteToTry != null) continue

      val routesLeft = routeSelection?.hasNext() ?: true
      if (routesLeft) continue

      val routesSelectionLeft = routeSelector?.hasNext() ?: true
      if (routesSelectionLeft) continue

      throw IOException("exhausted all routes")
    
  

下面涉及的类比较多,来几张图,从总体的认识一下

RouteSelector 是用来根据代理创建Route, 然后封装到Selection

Selection 针对某一个代理的 一系列的Route,切换代理后,该对象也会跟着改变

Route 一个Url可能会对应多个IP地址(DNS负载均衡),每个socket 对应一个Route 对象

Address 包含 一个Url,指定的Proxy(可为空), ProxySelector (可能会有许多Proxy)

这几个类的关系图

下图是,建立连接的流程图:


此图来源

代码六

代码六:
在ExchangeFinder.kt 类中
  /**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   *
   * This checks for cancellation before each blocking operation.
   */
  @Throws(IOException::class)
  private fun findConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean
  ): RealConnection 
    //如果当前的请求已经取消,就抛出异常
    if (call.isCanceled()) throw IOException("Canceled")
	//下面是使用RealCall 对象当前的连接
    // Attempt to reuse the connection from the call.
    val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
    if (callConnection != null) 
      var toClose: Socket? = null
      synchronized(callConnection) 
        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)
    

    //2、上面RealCall 对象的连接不可用,We need a new connection. Give it fresh stats.
    refusedStreamCount = 0
    connectionShutdownCount = 0
    otherFailureCount = 0

    // Attempt to get a connection from the pool.
    //callAcquirePooledConnection 函数,下面会详细分析
    if (connectionPool.callAcquirePooledConnection(address, call, null, false)) 
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    
	// 在连接池中也没有找到可用的,就去找Route,然后用Route创建一个连接
	// 寻找Route 的步骤:
	//1、先看nextRouteToTry 有没有值,(如果有值,就是上一次循环赋值的),拿出来使用
	//2、nextRouteToTry 为空,去Selection 中去找(Selection 就是一些列Route的集合)
	//3、如果Selection 中也没有,就去RouteSelector中,先找一个Selection,再在其中找Route
	// 如果是第一次进来,那么会到第三种情况,先创建一个RouteSelector,next() 后Selection,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) 
        //代码七 会详细介绍RouteSelector
        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.
      //注意这里参数,有传入routes 参数,
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) 
        val result = call.connection!!
        eventListener.connectionAcquired(call, result)
        return result
      

      route = localRouteSelection.next()
    

    // 代码如果执行到这里,就说明还没有找到可用连接,但是找到了Route
    // Connect. Tell the call about the connecting call so async cancels work.
    val newConnection = RealConnection(connectionPool, route)
    call.connectionToCancel = newConnection
    try 
     // 建立socket 连接,建立tls连接,建立tunnel
     // 见代码十:
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
     finally 
      call.connectionToCancel = null
    
    call.client.routeDatabase.connected(newConnection.route())

    // If we raced another call connecting to this host, coalesce the connections. This makes for 3
    // different lookups in the connection pool!
    // 最后一个参数是true,查找是否有多路复用(http2)的可用链接,如果有的话,就使用,并刚刚新创建的链接
    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) 
      val result = call.connection!!
      //把上面找到的route 保存起来,如果当前返回的连接不健康,下次就尝试这个route
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      //调用事件监听函数
      eventListener.connectionAcquired(call, result)
      return result
    

    synchronized(newConnection) 
       //放入连接池中
      connectionPool.put(newConnection)
      //见代码九分析
      call.acquireConnectionNoEvents(newConnection)
    
    //调用事件监听函数
    eventListener.connectionAcquired(call, newConnection)
    return newConnection
  

下面来详细分析一下RouteSelector、然后在代码八分析 callAcquirePooledConnection 是如何在连接池中选择一个连接的。

来看看RouteSelector 类的完整代码

代码七

代码七:
在RouteSelector.kt 类中

/**
 * Selects routes to connect to an origin server. Each connection requires a choice of proxy server,
 * IP address, and TLS mode. Connections may also be recycled.
 */
class RouteSelector(
  //在RealCall 中创建的address 对象,存放url,port,proxy,proxySelector(proxy 为空时 使用的)
  private val address: Address,
  private val routeDatabase: RouteDatabase,
  private val call: Call,
  private val eventListener: EventListener
) 
  /* State for negotiating the next proxy to use. */
  private var proxies = emptyList<Proxy>()
  private var nextProxyIndex: Int = 0

  /* State for negotiating the next socket address to use. */
  private var inetSocketAddresses = emptyList<InetSocketAddress>()

  /* State for negotiating failed routes */
  private val postponedRoutes = mutableListOf<Route>()

  init 
    //首先会调用这里
    resetNextProxy(address.url, address.proxy)
  

  // 把设置的代理都找出来,如果没有设置代理,则使用proxySelector,
  private fun resetNextProxy(url: HttpUrl, proxy: Proxy?) 
    fun selectProxies(): List<Proxy> 
      // If the user specifies a proxy, try that and only that.
      if (proxy != null) return listOf(proxy)

      // If the URI lacks a host (as in "http://</"), don't call the ProxySelector.
      val uri = url.toUri()
      if (uri.host == null) return immutableListOf(Proxy.NO_PROXY)

      // Try each of the ProxySelector choices until one connection succeeds.
      //注意这里的proxySelector( 类型是ProxySelector抽象类),对proxySelector 赋值 ,默认通过反射找到"sun.net.spi.DefaultProxySelector",如果为空,则使用NullProxySelector
      val proxiesOrNull = address.proxySelector.select(uri)
      //如果没有找到代理,就返回一个list,其中Proxy 的参数是直接相连,也就是没有代理
      if (proxiesOrNull.isNullOrEmpty()) return immutableListOf(Proxy.NO_PROXY)

      return proxiesOrNull.toImmutableList()
    

    eventListener.proxySelectStart(call, url)
    proxies = selectProxies()
    //这里赋初值,后面会根据这个字段来判断,是否还有代理
    nextProxyIndex = 0
    eventListener.proxySelectEnd(call, url, proxies)
  

  /**
   * Returns true if there's another set of routes to attempt. Every address has at least one route.
   */
  operator fun hasNext(): Boolean = hasNextProxy() || postponedRoutes.isNotEmpty()

  /** Returns true if there's another proxy to try. */
  private fun hasNextProxy(): Boolean = nextProxyIndex < proxies.size

  //代码六中,调用了这里,来创建一个Selection(Route 的集合)
  @Throws(IOException::class)
  operator fun next(): Selection 
    if (!hasNext()) throw NoSuchElementException()

    // Compute the next set of routes to attempt.
    val routes = mutableListOf<Route>()
    while (hasNextProxy()) 
      //即使没有代理,proxies也会有一个Proxy,此时nextProxyIndex == 0,所以hasNextProxy()为true,进入到这里
      // Postponed routes are always tried last. For example, if we have 2 proxies and all the
      // routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
      // all the good routes will we attempt the postponed routes.
      val proxy = nextProxy()
      for (inetSocketAddress in inetSocketAddresses) 
        val route = Route(address, proxy, inetSocketAddress)
        if (routeDatabase.shouldPostpone(route)) 
         //如果这个路由之前失败过,就加入到postponedRoutes 中
          postponedRoutes += route
         else 
         // 直接把route 加入routes
          routes += route
        
      
      //如果有没有失败过得route,就终止循环
      if (routes.isNotEmpty()) 
        break
      
    
	//所以的route都是失败过的,就把刚才postponedRoutes 加入到routes
    if (routes.isEmpty()) 
      // We've exhausted all Proxies so fallback to the postponed routes.
      // 把postponedRoutes 
      routes += postponedRoutes
      postponedRoutes.clear()
    
	//用routes 创建Selection 对象
    return Selection(routes)
  

  /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
  @Throws(IOException::class)
  private fun nextProxy(): Proxy 
    if (!hasNextProxy()) 
      throw SocketException(
          "No route to $address.url.host; exhausted proxy configurations: $proxies")
    
    val result = proxies[nextProxyIndex++]
    resetNextInetSocketAddress(result)
    return result
  

  /** Prepares the socket addresses to attempt for the current proxy or host. */
  @Throws(IOException::class)
  private fun resetNextInetSocketAddress(proxy: Proxy) 
    // Clear the addresses. Necessary if getAllByName() below throws!
    val mutableInetSocketAddresses = mutableListOf<InetSocketAddress>()
    inetSocketAddresses = mutableInetSocketAddresses

    val socketHost: String
    val socketPort: Int
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) 
      //如果是直接相连,或者是SOCKS 直接通过ip 和端口,访问后台
      socketHost = address.url.host
      socketPort = address.url.port
     else 
      // http 系列的代理
      val proxyAddress = proxy.address()
      require(proxyAddress is InetSocketAddress) 
        "Proxy.address() is not an InetSocketAddress: $proxyAddress.javaClass"
      
      socketHost = proxyAddress.socketHost
      socketPort = proxyAddress.port
    

    if (socketPort !in 1..65535) 
      throw SocketException(以上是关于OkHttp源码解析 (三)——代理和路由的主要内容,如果未能解决你的问题,请参考以下文章

Retrofit源码解析

OKHttp分发器源码分析(两个版本)

OKHttp分发器源码分析(两个版本)

OKHttp分发器源码分析(两个版本)

源码系列--OkHttp

ConnectInterceptor 解析——OkHttp 源码详解