×

OKHttp全解析系列(七) -- OKHttp中的Route和RouteSelector

96
野生的安卓兽
2017.11.01 11:19* 字数 6936

本篇文章主要讲Okhttp中Route、Proxy、Address、RouteSelector相关的概念。
目的是让andorid开发者可以从更多的角度认识OkHttp的源码实现原理。

研究OkHttp就要站在编写框架的角度考虑如何实现网络编程框架。有两个思想:
  • 1.分析网络框架,要站在网络开发者和框架开发者的的角度来思考问题。
  • 2.计算机网络是分层的思想来实现的,研究网络框架就要跳出计算机网络中的应用层,从计算机网络中的多层角度去分析OkHttp框架。实际上,OkHttp框架是从传输层到应用层的实现。以分层的角度学习网络框架的实现,OkHttp中很多难理解的概念就变得容易多了。

下面放上两张图片,已利于理解OkHttp中从Tcp到Http的过程。

三种网络分层协议对比
基于OSI七层模型的网络协议所在层
再介绍HTTP协议相关的概念

这部分若是觉得已经了解了,可以略过,如果后面看不懂再回来细读这里的概念。

  • 1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。
  • 2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。
  • 3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号
  • 4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。
  • 5.资源(Resource):由URI标识的网络数据对象或服务。
  • 6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。
  • 7.客户机(Client):一个为发送请求目的而建立连接的应用程序。
  • 8.用户代理(Useragent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。
  • 9.服务器(Server):一个接受连接并对请求返回信息的应用程序。
  • 10.源服务器(Originserver):是一个给定资源可以在其上驻留或被创建的服务器。
  • 11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。
  • 12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。 网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
  • 13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。
  • 14.路由(Route)*:是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程。

这篇文章主要讲Route,下边从多个角度理解一下路由的含义:

路由(routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程 。路由工作在OSI参考模型第三层——网络层的数据包转发设备。路由器通过转发数据包来实现网络互连。虽然路由器可以支持多种协议(如TCP/IP、IPX/SPX、AppleTalk等协议),但是在我国绝大多数路由器运行TCP/IP协议。路由器通常连接两个或多个由IP子网或点到点协议标识的逻辑端口,至少拥有1个物理端口。路由器根据收到数据包中的网络层地址以及路由器内部维护的路由表决定输出端口以及下一跳地址,并且重写链路层数据包头实现转发数据包。路由器通过动态维护路由表来反映当前的网络拓扑,并通过网络上其他路由器交换路由和链路信息来维护路由表。

所谓“路由”,是指把数据从一个地方传送到另一个地方的行为和动作,而路由器,正是执行这种行为动作的机器,它的英文名称为Router,是一种连接多个网络或网段的网络设备,它能将不同网络或网段之间的数据信息进行“翻译”,以使它们能够相互“读懂”对方的数据,从而构成一个更大的网络。

简单的讲,路由器主要有以下几种功能
第一,网络互连,路由器支持各种局域网和广域网接口,主要用于互连局域网和广域网,实现不同网络互相通信;
第二,数据处理,提供包括分组过滤、分组转发、优先级、复用、加密、压缩和防火墙等功能;
第三,网络管理,路由器提供包括配置管理、性能管理、容错管理和流量控制等功能

为了完成“路由”的工作,在路由器中保存着各种传输路径的相关数据--路由表(Routing Table),供路由选择时使用。路由表中保存着子网的标志信息、网上路由器的个数和下一个路由器的名字等内容。路由表可以是由系统管理员固定设置好的,也可以由系统动态修改,可以由路由器自动调整,也可以由主机控制。在路由器中涉及到两个有关地址的名字概念,那就是:静态路由表和动态路由表。由系统管理员事先设置好固定的路由表称之为静态(static)路由表,一般是在系统安装时就根据网络的配置情况预先设定的,它不会随未来网络结构的改变而改变。动态(Dynamic)路由表是路由器根据网络系统的运行情况而自动调整的路由表。路由器根据路由选择协议(Routing Protocol)提供的功能,自动学习和记忆网络运行情况,在需要时自动计算数据传输的最佳路径。

为了简单地说明路由器的工作原理,现在我们假设有这样一个简单的网络。如图所示,A、B、C、D四个网络通过路由器连接在一起。
现在我们来看一下在如图所示网络环境下路由器又是如何发挥其路由、数据转发作用的。现假设网络A中一个用户A1要向C网络中的C3用户发送一个请求信号时,信号传递的步骤如下:
第1步:用户A1将目的用户C3的地址C3,连同数据信息以数据帧的形式通过集线器或交换机以广播的形式发送给同一网络中的所有节点,当路由器A5端口侦听到这个地址后,分析得知所发目的节点不是本网段的,需要路由转发,就把数据帧接收下来。
第2步:路由器A5端口接收到用户A1的数据帧后,先从报头中取出目的用户C3的IP地址,并根据路由表计算出发往用户C3的最佳路径。因为从分析得知到C3的网络ID号与路由器的C5网络ID号相同,所以由路由器的A5端口直接发向路由器的C5端口应是信号传递的最佳途经。
第3步:路由器的C5端口再次取出目的用户C3的IP地址,找出C3的IP地址中的主机ID号,如果在网络中有交换机则可先发给交换机,由交换机根据MAC地址表找出具体的网络节点位置;如果没有交换机设备则根据其IP地址中的主机ID直接把数据帧发送给用户C3,这样一个完整的数据通信转发过程也完成了。
从上面可以看出,不管网络有多么复杂,路由器其实所作的工作就是这么几步,所以整个路由器的工作原理基本都差不多。当然在实际的网络中还远比上图所示的要复杂许多,实际的步骤也不会像上述那么简单,但总的过程是这样的。

再来了解下代理,这可是咱们理解Route的重点

代理的功能
1.突破自身IP访问限制,访问国外站点。教育网、169网等网络用户可以通过代理访问国外网站。
2.访问一些单位或团体内部资源,如某大学FTP(前提是该代理地址在该资源 的允许访问范围之内),使用教育网内地址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务。
3.突破中国电信的IP封锁:中国电信用户有很多网站是被限制访问的,这种限制是人为的,不同Serve对地址的封锁是不同的。所以不能访问时可以换一个国 外的代理服务器试试。
4.提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时, 则直接由缓冲区中取出信息,传给用户,以提高访问速度。
5.隐藏真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。

从代理服务器的角度去理解代理
代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站。在一般情况下,我们使用网络浏览器直接去连接其他Internet站点取得网络信息时,须送出Request信号来得到回答,然后对方再把信息以bit方式传送回来。代理服务器是介于浏览器和Web服务器之间的一台服务器,有了它之后,浏览器不是直接到Web服务器去取回网页而是向代理服务器发出请求,Request信号会先送到代理服务器,由代理服务器来取回浏览器所需要的信息并传送给你的浏览器。而且,大部分代理服务器都具有缓冲的功能,就好象一个大的Cache,它有很大的存储空间,它不断将新取得数据储存到它本机的存储器上,如果浏览器所请求的数据在它本机的存储器上已经存在而且是最新的,那么它就不重新从Web服务器取数据,而直接将存储器上的数据传送给用户的浏览器,这样就能显著提高浏览速度和效率。更重要的是:Proxy Server(代理服务器)是Internet链路级网关所提供的一种重要的安全功能,它的工作主要在开放系统互联(OSI)模型的对话层。

代理分为两种类型,一种是SOCKS代理,另一种是HTTP代理。

  • 对于SOCKS代理,在HTTP的场景下,代理服务器完成TCP数据包的转发工作。
  • 而HTTP代理服务器,在转发数据之外,还会解析HTTP的请求及响应,并根据请求及响应的内容做一些处理。
  • socks 是在会话层传输协议,http代理是在应用层传输协议.
Socks代理是相对了解较少的代理,下面详细介绍一下

SOCKS:防火墙安全会话转换协议 (Socks: Protocol for sessions traversal across firewall securely) SOCKS 协议提供一个框架,为在 TCP 和 UDP 域中的客户机/服务器应用程序能更方便安全地使用网络防火墙所提供的服务。协议工作在OSI参考模型的第5层(会话层),使用UDP协议传输数据,因而不提供如传递 ICMP 信息之类的网络层网关服务。

利用网络防火墙将组织内部的网络结构与外部网络如 INTERNET 中有效地隔离开来,这种方法正变得逐渐流行起来。这些防火墙系统通常以应用层网关的形式工作在网络之间,提供受控的 TELNET 、 FTP 、 SMTP 等的接入。 SOCKS 提供一个通用框架来使这些协议安全透明地穿过防火墙

SOCKSv5 为这些协议穿越提供了有力的认证方案,而 SOCKSv4 为 TELNET 、FTP 、HTTP 、WAIS 和 GOPHER 等基于 TCP 协议的客户/服务器程序仅仅提供了一个不安全防火墙穿越。新的协议 SOCKS v5 在 SOCKSV4基础上作了进一步扩展,从而可以支持 UDP ,并对其框架规定作了扩展,以支持安全认证方案。同时它还采用地址解析方案 (addressing scheme) 以支持域名和 IPV6 地址。

为了实现这个 SOCKS 协议,通常需要重新编译或者重新链接基于 TCP 的客户端应用程序以使用 SOCKS 库中相应的封装程序。
采用socks协议的代理服务器就是SOCKS服务器,是一种通用的代理服务器。Socks是个电路级的底层网关,是 DavidKoblas在1990年开发的,此后就一直作为Internet RFC标准的开放标准。Socks不要求应用程序遵循特定的操作系统平台,Socks 代理与应用层代理、 HTTP 层代理不同,Socks代理只是简单地传递数据包,而不必关心是何种应用协议(比如FTP、HTTP和NNTP请求)。所以,Socks代理比其他应用层代理要快得多。它通常绑定在代理服务器的1080端口上。如果您在企业网或校园网上,需要透过防火墙或通过代理服务器访问Internet就可能需要使用 SOCKS。一般情况下,对于拨号上网用户都不需要使用它。注意,浏览网页时常用的代理服务器通常是专门的http代理,它和SOCKS是不同的。因此,您能浏览网页不等于您一定可以通过SOCKS访问Internet。常用的防火墙,或代理软件都支持SOCKS,但需要其管理员打开这一功能。如果您不确信您是否需要SOCKS或是否有SOCKS可用,请与您的网络管理员联系。

为了使用socks,您需要了解一下内容:
① SOCKS服务器的IP地址
② SOCKS服务所在的端口
③ 这个SOCKS服务是否需要用户认证?如果需要,您要向您的网络管理员申请一个用户和口令
知道了上述信息,您就可以把这些信息填入“网络配置”中,或者在第一次登记时填入,您就可以使用socks代理了。

在实际应用中SOCKS代理可以用作为:电子邮件、新闻组软件、网络传呼ICQ、网络聊天MIRC和使用代理服务器上联众打游戏等等各种游戏应用软件当中。
Socks默认端口1080,Socks5是socks协议目前最新的一个版本。

有了上面的网络基础知识,下面开始讲解OkHttp中的概念:
Route

Route 包含三个属性

  • Address 描述建立连接所需的配置信息,
  • Proxy 代理服务器的信息 代理 有三种类型
  • InetSocketAddress 连接的目标地址

Route描述的是直接与服务器建立连接的地址,每一个Route对象描述的 是直接与服务器建立连接单位。

请求的url经过dns解析可以解析出多个ip地址,服务器一般会使用代理设置,这个代理设置会有几种类型,根据不同的代理类型,也会产生多个Route的存在。

Route这里提到了代理和Dns两个概念

Dns是做域名解析使用的,传入一个url会解析出多个ip地址,这个url对应的ip地址就是在dns服务器做解析得到的
如:http://www.baidu.com 会经过dns解析出220.181.111.188和220.181.112.244两个服务器ip地址

OkHttp3中抽象出Route来描述网络数据包的传输路径,最主要还是要描述直接与其建立TCP连接的目标端点。

主要通过 代理服务器的信息proxy ,及 连接的目标地址inetSocketAddress 描述路由。连接的目标地址inetSocketAddress 根据代理类型的不同而有着不同的含义,这主要是由不同代理协议的差异而造成的。

  • 对于无需代理的情况, 连接的目标地址inetSocketAddress 中包含HTTP服务器经过了DNS域名解析的IP地址及协议端口号;
  • 对于SOCKS代理,其中包含HTTP服务器的域名及协议端口号;
  • 对于HTTP代理,其中则包含代理服务器经过域名解析的IP地址及端口号。
Address

Address 属性:

  • HttpUrl: 发送请求的地址,主机名和端口号,http、https相关的信息
  • Dns :域名解析服务
  • SocketFactory: 为新连接创建Socket的工厂,OkHttp中有默认的实现,直接new Socket(),
  • Authenticator: 客户端的代理认证
  • List<Protocol>: 客户端支持的协议,默认支持http/1.1和http2
  • List<ConnectionSpec>:具体的安全与连接是由ConnectionSpec接口决定
  • ProxySelector:代理选择器,这个是在OkHttpCilent中传入的,如果不填写会有默认的实现具体可以看java.net.ProxySelector具体的实现是用的
    Class c = Class.forName("sun.net.spi.DefaultProxySelector"); 这个实现的。
  • Proxy: 代理,在OkHttpClient中传入的参数,返回明确的Http代理,或者null使用ProxySelector的选择代理
  • SSLSocketFacotyHostnameVerifierCertificatePinner:这三个是https相关的配置

Address类是收集网络请求信息的类,是配置OkHttpClient时或者请求服务端时收集的网络请求信息。Address中的代码是一个构造方法把所有参数传入进来,赋值给对应的属性,剩下的是这些属性的get方法。Address的创建的地方是在RetryAndFollowUpInterceptor这个拦截器中有创建Address的方法

@Override
public Response intercept(Chain chain) throws IOException {

... ...
//建立连接使用Address
  streamAllocation = new StreamAllocation(
      client.connectionPool(), createAddress(request.url()), callStackTrace);
... ...
}
//收集建立连接的信息
private Address createAddress(HttpUrl url) {
  SSLSocketFactory sslSocketFactory = null;
  HostnameVerifier hostnameVerifier = null;
  CertificatePinner certificatePinner = null;
  if (url.isHttps()) {
    sslSocketFactory = client.sslSocketFactory();
    hostnameVerifier = client.hostnameVerifier();
    certificatePinner = client.certificatePinner();
  }

  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类不容易理解是因为在网路框架中有Route、有Address,到底哪个才是与服务器真正建立连接的呢?
Route是真正与服务器建立连接的对象,Address是收集网络请求使用的。

客户端与服务器建立连接要经过多层处理,每一层处理不同的事情,对应着Okhttp中的5个拦截器
1.收集网络请求需要的配置信息 Addres在这一层
2.这次请求的请求头部信息进行转换。
3.网络缓存中是否能处理这次请求
4.与服务器建立连接,ur可以解析出多个服务器地址(由于代理和dns解析的原因) ,尝试与解析出的多个服务器建立连接。 Route和RouteSelector在这一层
5.与服务器通讯。

RouteDataBase

RouteDatabase

  • Set<Route> failedRoutes 记录连接失败的路由信息

RouteDatabase里边就一个集合存储连接失败的路由,下次请求时优先请求其他路由。RouteDataBase是维护接失败的路由的信息,以避免浪费时间去连接一些不可用的路由。 RouteSelector 借助于RouteDatabase 维护失败的路由的信息。

RouteSelector

RouteSelector属性:
Address address; 网络请求相关的信息
RouteDatabase routeDatabase; 存储连接Route失败的信息
Proxy lastProxy; //最近选择的代理
InetSocketAddress lastInetSocketAddress; //最近选择的代理对应的地址
List<Proxy> proxies; //代理列表
int nextProxyIndex; //代理的索引
List<InetSocketAddress> inetSocketAddresses; //代理列表对应的地址
int nextInetSocketAddressIndex; //选择下一个代理的索引
List<Route> postponedRoutes; //记录最近连接失败的路由

存在的原因:
  • Route 的对应三种代理类型的实现方式不一样。
  • 一个域名对应多个ip的域名解析是比较常见的。
  • Route连接失败的处理机制。
完成三件事情是:
  • 收集所有可用的路。
  • 选择可用的路由。
  • 维护连接失败的路由的信息。
原因详细

HTTP请求处理过程中所需的TCP连接建立过程,主要是找到一个Route,然后依据代理协议的规则与特定目标建立TCP连接。对于无代理的情况,是与HTTP服务器建立TCP连接;对于SOCKS代理及HTTP代理,是与代理服务器建立TCP连接,虽然都是与代理服务器建立TCP连接,而SOCKS代理协议与HTTP代理协议做这个动作的方式又会有一定的区别。

借助于域名解析做负载均衡已经是网络中非常常见的手法了,因而,常常会有相同域名对应不同IP地址的情况。同时相同系统也可以设置多个代理,这使Route的选择变得复杂起来。

在OkHttp中,对Route连接失败有一定的错误处理机制。OkHttp会逐个尝试找到的Route建立TCP连接,直到找到可用的那一个。这同样要求,对Route信息有良好的管理。

Route 和RouteSelector做的事情
代理服务器的收集是在创建 RouteSelector 完成的;而一个特定代理服务器选择下的连接的目标地址 收集则是在选择Route时根据需要完成的。

RouteSelector是对要请求的目标地址中解析出来的多个Route进行选择,如果一个连接不成功就加入RouteDatabase,然后尝试请求下一个Route。如果已经有建立的连接,就复用之前的连接。

到这里RouteSelector应该有了个认识了,是选择Route的,Address是存储网络请求信息的,Route才是目标机器的地址。还有一个RouteDataBase是选择优化使用的,RouteSelector就是选择Route用的。

这里边还有一个Proxy以及ProxySelector的概念,这两个类不是OkHttp中的类是在jdk中的java.net.Proxy
下面是Proxy的一段代码:

    /**
     * Represents the proxy type.
     *
     * @since 1.5
     */
    public enum Type {
        /**
         * Represents a direct connection, or the absence of a proxy.
         */
        DIRECT,
        /**
         * Represents proxy for high level protocols such as HTTP or FTP.
         */
        HTTP,
        /**
         * Represents a SOCKS (V4 or V5) proxy.
         */
        SOCKS
    };

从这段代码可以了解到这个是说代理的类型有三种,一种是直接连接,一种是Http连接,还有一种的Socks连接,对应着前面介绍的代理的类型。
还有一个ProxySelector(java.net.ProxySelector);这里是android-25源码

   /**
     * The system wide proxy selector that selects the proxy server to
     * use, if any, when connecting to a remote object referenced by
     * an URL.
     *
     * @see #setDefault(ProxySelector)
     */
    private static ProxySelector theProxySelector;

    static {
        try {
            Class c = Class.forName("sun.net.spi.DefaultProxySelector");
            if (c != null && ProxySelector.class.isAssignableFrom(c)) {
                theProxySelector = (ProxySelector) c.newInstance();
            }
        } catch (Exception e) {
            theProxySelector = null;
        }
    }

这个ProxySelector是系统实现的sun.net.spi.DefaultProxySelector.
如果不能理解这个ProxySelector那就把它比喻成DNS。给dns传入一个域名,dns就可以查找出多个ip地址和端口号。这个ProxySelector传入一个地址,会返回多个Proxy类型,这个如果不理解可以亲自尝试一下,使用一下这个类,或者用OkHttp源码打个断点试下就会明白了。

具体的在OkHttp源码位置在:RouteSelector.java

/** Prepares the proxy servers to try. */
  private void resetNextProxy(HttpUrl url, Proxy proxy) {
    if (proxy != null) {
      // If the user specifies a proxy, try that and only that.
      proxies = Collections.singletonList(proxy);
    } else {
      // Try each of the ProxySelector choices until one connection succeeds.
      List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
      proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
          ? Util.immutableList(proxiesOrNull)
          : Util.immutableList(Proxy.NO_PROXY);
    }
    nextProxyIndex = 0;
  }

这一行 List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
address是手机网络请求信息的,其中就收集了ProxySelector,调用select方法,就会返回多个代理信息。

此处有一个计算机网络分层的思想

在应用层,客户端和服务器感受到的一次网络请求,网络就是一次通讯。但是在网络框架的实现中,是在应用层客户端所谓的一次请求和应答其实在网络中,客户端机器与服务器通讯了很多遍。
例如:dns和proxy就需要客户端与网络中的其他节点需要通讯多次才能得到结果一样。

RouteSelector这个类的源码内容就是对当前的Route信息进行选择,如果这个Route连接不成功,就换一个,Route连接成功与否并不在RouteSelector的工作范围,有其他层来处理与服务器是否联通的问题。计算机中网络的每一层都只做它这一层需要做的事情。顺便说一句没什么这么分层,是因为计算机网络太复杂,需要将其拆解,每一层解决一定的问题。

OKHttp系列
Web note ad 1