继前面的几篇OKhttp的拦截器简单分析之后,对于后续Okhttp之间的分析自己也着实琢磨了一段时间,是分析RealConnection?还是ConnectionPool,随着对Okhttp源码的深入分析发现,着实是千丝万缕,有点凌乱,所以对于Okttp的源码分析只能一点点的抽丝剥茧,本篇就来简单分析一下RouteSelector这个类。

RouteSelector的初始化

RouteSelector这个类是在StreamAllocation里面初始化的:

 public StreamAllocation(ConnectionPool connectionPool, Address address, Object callStackTrace) {
   routeSelector = new RouteSelector(address,  routeDatabase()
  }

通过上面的代码可以发现RouteSelector对象的初始化需要Address和RouteDatabase这两个对象,Address这个对象是神马时候初始化的呢?在我们使用OKhttp进行访问的时候需要是需要创建Request对象的,而Request在通过Request.Builder的构建中有这么一个重载方法:

 public Builder url(String url) {
      ....
      HttpUrl parsed = HttpUrl.parse(url);
      ....
    }

通过Request.Builder的url我们得到一个HttpUrl,StreamAllocation对象持有一个Address的引用address,而在拦截器RetryAndFollowUpInterceptor在执行intercept时候初始化了StreamAllocation对象,并且调用了RetryAndFollowUpInterceptor的createAddress(HttpUrl )方法创建一个Address对象作为StreamAllocation 对象中address值,最终这个address值交给了RouteSelector!代码如下:

//RetryAndFollowUpInterceptor拦截器的intercept方法
public Response intercept(Chain chain) {

 streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);
}
//创建Address 对象交给streamAllocation.address引用
 private Address createAddress(HttpUrl url) {
   //省略了部分代码
    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

Address的构建简单分析

通过Address构造器的分析我们可以发现一个url构成的Address对象有:主机名host、端口号port、Dns、代理服务器proxy等元素(其他元素比如hostnameVerifier没有说明是因为与本博文内容无关)。

Dns:是一个接口,如果OkhttpClient在使用中没有配置自己的DNS实现则使用Okttp默认DNS.SYSTEM对象:

Dns SYSTEM = new Dns() {
   @Override
   public List<InetAddress> lookup(String hostname) {
      return Arrays.asList(InetAddress.getAllByName(hostname));
    }
  };

该接口实现也很简单,就是调用了根据url中的主机名hostname通过getAllByName**获取InetAddress对象的集合*。因为有些计算机会有多个Internet地址,getAllByName方法包含所有对应此主机名的地址,通常有多个ip地址的主机大多数都是有着非常高吞吐量的web服务器(服务器集群)。比如如果getAllByName(“www.microsoft.com”)的话,打印List 集合中InetAddress对象的toString方法你可以发现有多个如下格式的输出,格式为(hostname/ip地址)*:

www.microsoft.com/aa.aaa.aa.aaa
www.microsoft.com/xx.xxx.xx.xxx
www.microsoft.com/yy.yyy.yy.yyy

到这儿准备对RouteSelector分析的准备工作算是完成,我们看看RouteSelector构造器都做了些什么:

public RouteSelector(Address address, RouteDatabase routeDatabase) {
    this.address = address;
    this.routeDatabase = routeDatabase;

    resetNextProxy(address.url(), address.proxy());
  }

除了简单的为属性赋值之外,还调用了resetNextProxy方法,该方法的主要作用就是初始化RouteSelector类中的代理服务器集合proxies

  private List<Proxy> proxies = Collections.emptyList();
  private void resetNextProxy(HttpUrl url, Proxy proxy) {
    if (proxy != null) {//客户端配置了自己的代理
      proxies = Collections.singletonList(proxy);
    } else {
       //通过ProxySelector.getDefault();对象来获取默认代理
      List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
      proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
          ? Util.immutableList(proxiesOrNull)
          : Util.immutableList(Proxy.NO_PROXY);/
    }
    //访问proxies集合的下标,初始化为0
    nextProxyIndex = 0;
  }

客户端在配置OkttCilent对象的时候是可以通过Okhttp的Builder对象来创建自己的代理服务器,用户配置的代理服务器对象proxy会交给上文所说的Address对象,进而交给RouteSelector.当然用户如果没有配置代理服务器对象,则OhttpClinet使用ProxySelector.getDefault()这个ProxySelector对象的select方法返回默认的代理服务器集合,当然如果没有找到代理服务器,则使用Proxy.NO_PROXY,表示当前Address对网络的请求完全绕开代理服务器,直接连接远程主机!

此时RouteSelector的初始化工作算是完全搞定!那么RouteSelector是做什么的呢?又是如何做的呢?顾名思义RouteSelector的作用就是Select Route(选择路由),在OKhttp中其实也就是返回一个可用的Route对象。下面通过其next方法()具体分析之。

public Route next() throws IOException {
    // 没有有多余的ip地址
    if (!hasNextInetSocketAddress()) {
     //如果多余的代理服务器
      if (!hasNextProxy()) {
       //如果没有延迟的路由
        if (!hasNextPostponed()) {
          throw new NoSuchElementException();
        }
        //获取延迟的路由
        return nextPostponed();
      }
      //如果没有多余的ip地址并且有其他代理服务器,调用nextProxy
      //获取下一个代理服务器
      lastProxy = nextProxy();
    }

    //获取ip地址列表中下一个ip地址构成的InetSocketAddress对象
    lastInetSocketAddress = nextInetSocketAddress();

   //创建一个Route 对象
    Route route = new Route(address, lastProxy, lastInetSocketAddress);
    。。。。省略部分代码。。。
    //返回一路由
    return route;
  }

上面的代码逻辑也很简单:

1、如果没有多余的ip地址(还记得上文说的InetAddress.getAllByName()吗?) 则判断是否有多余的代理服务器,如果有多余的代理服务器的话,则调用nextProxy()从上文说的proxies获取一个Proxy代理服务器对象,赋值给lastProxy !

2、调用nextInetSocketAddress方法从inetSocketAddresses这个list集合中获取一个InetSocketAddress对象,赋值给lastInetSocketAddress引用!

3、将address对象、lastProxy对象和lastInetSocketAddress组成一个Route对象并返回!

看到这儿也许你会问inetSocketAddresses这个集合是什么鬼?是神马时候初始化的?前文神马没有说明,其实这个方法集合的初始化是在nextProxy()方法里,下面来分析nextProxy方法:

private Proxy nextProxy() throws IOException {
    //从 proxies获取一个代理服务器
    Proxy result = proxies.get(nextProxyIndex++);
    //重置inetSocketAddresses集合
    resetNextInetSocketAddress(result);
    return result;
  }

继续看看resetNextInetSocketAddress方法:

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {

    //初始化InternetSocketAdds集合
    inetSocketAddresses = new ArrayList<>();
   //主机名
    String socketHost;
    //端口号
    int socketPort;
    //如果不使用代理或者使用SOCKS的代理服务器
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      socketHost = address.url().host();//获取主机
      socketPort = address.url().port();//获取端口号
    } else {//如果HTPP代理服务器
      //获取SocketAddress对象
      SocketAddress proxyAddress = proxy.address();
      。。。。
      //转换成InetSocketAddress 对象
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
      //获取代理服务器的主机名和端口号
      socketHost = getHostString(proxySocketAddress);
      socketPort = proxySocketAddress.getPort();
    }
    。。。。。
   //如果是socks代理,直接放入集合
    if (proxy.type() == Proxy.Type.SOCKS) {
         inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
    } else {//如果是http代理或者是无代理连接

      //获取ip地址列表
      List<InetAddress> addresses = address.dns().lookup(socketHost);
      。。。。。
      //根据ip列表地址创建多个InetSocketAddress
      for (int i = 0, size = addresses.size(); i < size; i++) {
        InetAddress inetAddress = addresses.get(i);
        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
      }
    }

    //集合索引重置为0
    nextInetSocketAddressIndex = 0;
  }

可以看出resetNextInetSocketAddress方法也很简单

1、根据proxies选中的Proxy代理服务器来创建nextInetSocketAddress集合。(也就是说如果有下一个代理的话,上一个代理服务器的InteSocketAddress数据会设置空,毕竟nextInetSocketAddress集合重新初始化了)

2、根据Proxy的类型来获取服务器主机的主机名和端口号,如果没有使用代理或者是使用了SOCKS代理服务器,则根据url直接获取主机名和端口号!如果使用了HTTP的代理,则获取代理服务器的地址(SocketAddress),然后通过InetSocketAddress 获取代理服务器的主机名和端口号!

3、得到主机名socketHost 和端口号socketPort 之后如果是SOCKETS代理服务器,则通过这两个信息创建一个InetSocketAddress对象添加到集合里。如果使用的是Http代理服务器或者没有使用代理服务器,则通过上文所说的DNS根据得到的主机名socketHost 获取ip地址列表(List《InetAddress 》集合)来为每一个ip组成的InetAddress对象,构建一个InetSocketAddress,并添加到inetSocketAddresses中去!

到此为止RouteSelector的分析就介绍完毕,综合上面的讲解可以知道RouteSelector的主要作用有两个:

1、选择代理服务器Proxy

2、对有多个ip地址的(代理)服务器进行ip选择(只不过该ip地址用InetSocketAddress表示而已)

3、将选中的(代理)服务器和ip地址(InetSocketAddress),及上文所说的Address构成Route对象并返回该对象。

可以用图简单来表示一下:

其实如果读过《Http权威指南》这本书的话,这个类其实有点**类似于**DNS重定向功能的功能,简单的决定一个host的ip列表中选中哪一个ip作为目标主机地址来使用:

Okhttp之RouteSelector简单解析的更多相关文章

  1. Okhttp源码简单解析(一)

    业余时间把源码clone下来大致溜了一遍,并且也参阅了其余大神的博客,在这里把自己的心得记录下来共享之,如有不当的地方欢迎批评指正.本文是Okttp源码解析系列的第一篇,不会深入写太多的东西,本篇只是 ...

  2. OkHttp之ConnectInterceptor简单分析

    在< Okhttp之CacheInterceptor简单分析 >这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览 ...

  3. OkHttp之BridgeInterceptor简单分析

    在< Okhttp源码简单解析(一) >这篇博客简单分析了Okhttp请求的执行流程,通过该篇博客我们知道OkHttp的核心网络请求中内置"拦截器"发挥了重大作用:本篇 ...

  4. Okhttp之CacheInterceptor简单分析

    <OkHttp之BridgeInterceptor简单分析 >简单分析了BridgeInterceptor的工作原理,在Okhttp的拦截器链上BridgeInterceptor的下一个拦 ...

  5. 对 cloudwu 简单的 cstring 进行简单解析

    题外话 以前也用C写过字符串,主要应用的领域是,大字符串,文件读取方面.写的很粗暴,用的凑合着.那时候看见云风前辈的一个开源的 cstring 串. 当时简单观摩了一下,觉得挺好的.也没细看.过了较长 ...

  6. 基于DOM的XSS注入漏洞简单解析

    基于DOM的XSS注入漏洞简单解析http://automationqa.com/forum.php?mod=viewthread&tid=2956&fromuid=21

  7. 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...

  8. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  9. List<T>集合的Sort自定义排序用法简单解析

    List<T>集合的Sort自定义排序用法简单解析: 如下:一系列无序数字,如果想要他们倒序排列,则使用如下代码: 那么如何理解这段代码呢? (x,y)表示相邻的两个对象,如果满足条件:x ...

随机推荐

  1. 20145303刘俊谦 《Java程序设计》第三周学习总结

    20145303刘俊谦 <Java程序设计>第三周学习总结 教材学习内容总结 1.类与对象: 类:对现实生活中事物的描述,定义类时用关键词class 对象:这类事物实实在在存在的个体,利用 ...

  2. 20145321 《Java程序设计》第一周学习总结

    20145321 <Java程序设计>第1周学习总结 教材学习内容总结 第一章 1.三大平台:Java SE.Java EE .Java ME 2.Java SE:由JVM.JRE.JDK ...

  3. 20145328 《Java程序设计》第10周学习总结

    20145328 <Java程序设计>第10周学习总结 资料学习内容总结 网络编程 13.1 网络概述 网络编程技术是当前一种主流的编程技术,随着联网趋势的逐步增强以及网络应用程序的大量出 ...

  4. Activiti工作流与spring集成

    一.前言 前面Activiti工作流的学习,说明了Activiti的基本应用,在我们开发中可以根据实际的业务参考Activiti的API去更好的理解以及巩固.我们实际的开发中我们基本上都使用sprin ...

  5. 洛谷 P2015 二叉苹果树(codevs5565) 树形dp入门

    dp这一方面的题我都不是很会,所以来练(xue)习(xi),大概把这题弄懂了. 树形dp就是在原本线性上dp改成了在 '树' 这个数据结构上dp. 一般来说,树形dp利用dfs在回溯时进行更新,使用儿 ...

  6. codeforces 1A - math - ceil

    2017-08-24 15:42:30 writer: pprp 感觉自己好菜啊,这个题都没有做的很好 题意很简单,用a * a 的地砖,将 n * m 的地板铺满,问最少需要多少个地砖? 一开始打算 ...

  7. springboot Actuator健康检查

    通过情况下,如我们想在系统中添加一个健康检查的接口,我们怎么做呢? 我们会新建一个类,或在已存在类的基础上添加检测接口. package com.crhms.medicareopinion; impo ...

  8. harbor 管理Helm Chart包

    官方网站:https://github.com/goharbor/harbor官方用户手册:https://github.com/goharbor/harbor/blob/master/docs/us ...

  9. Windows中使用wget整站下载

    weget wget安装 Windows下载 点击下载   https://eternallybored.org/misc/wget/ 会跳转到wget的下载页,根据自己电脑选择下载的文件,我下载的版 ...

  10. 手动建立Mysql表实体类技巧

    首先执行一条sql语句,也可以在开发中插入数据.修改数据或者查询数据的某个属性时使用. select sc.COLUMN_NAME from information_schema.COLUMNS as ...