开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解:

Okhttp源码简单解析(一)

OkHttp之BridgeInterceptor简单分析

Okhttp之CacheInterceptor简单分析

OkHtp之ConnectInterceptor简单分析

Okhttp之RouteSelector简单解析

闲言少叙,书归正传,在ConnectionInterceptor这篇博文的分析中我们可以得到一下结论:

1、在ConnectionInterceptor的intercept方法中通过调用StreamAllocation对象的newStream方法。

2、在newStream方法里面会有一个while循环,执行findConnection方法查找一个RealConnection.

findConnection的初步解释可以参考《OkHtp之ConnectInterceptor简单分析

在从连接池获ConnectionPool获取RealConnection对象的时候,get方法会调用两次(对findConection剔除了与本博文无关的代码之后如下所示):

private RealConnection findConnection(。。。){
    Route selectedRoute;
    synchronized (connectionPool) {
        。。。。
      //从连接池获取一个连接,此时最后一个参数route传的为null
      Internal.instance.get(connectionPool, address, this, null);
      //成功从连接池中获取一个连接,返回之
      if (connection != null) {
        return connection;
      }
      //当前对象使用的路由对象
      selectedRoute = route;
    }

    //选中一个路由
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }
    RealConnection result;
    synchronized (connectionPool) {
      //从缓冲池中获取对象
      Internal.instance.get(connectionPool, address, this, selectedRoute);
        //缓存池中有此连接
      。。。。。
    }
    。。。。。
    return result;
  }

很清晰的可以看出第一次尝试从连接池中获取 RealConnection对象的时候route传null,如果此时没有获取到可用的链接,则选中一个路由后(routeSelector.next()),再次重连接池中获取RealConnection,(注意在调用连接池的时候将自身(this)也就是StreamAllocation对象也传了过去)。 (注:关于路由选择这块详情参考《Okhttp之RouteSelector简单解析》)

先看看是怎么从连接池中获取链接的:

//此方法为ConnectionPool对象的方法
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    //遍历连接池
    for (RealConnection connection : connections) {
      //判断链接是否合格
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }

上面的代码也很简单:

1、循环遍历连接池connections(本质是一个ArrayDeque),从中获取一个可用链接。

2、如果为可用链接,则调用streamAllocation的acquire方法。

在这里有两个重要的方法:一个是acquire方法;一个是isEligible,它根据地址address和路由route判断链接是否为可用(或可复用)。先分析分析StreamAllocation对象的acquire方法:

private RealConnection connection;
public void acquire(RealConnection connection) {
   if (this.connection != null) throw new IllegalStateException();
    this.connection = connection;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

该方法很简单:就是把从连接池中获取到的RealConnection对象赋值给StreamAllocation的connection属性,然后把StreamAllocation对象的弱引用添加到RealConnection对象的allocations集合中去,所以根据RealConnection对象的allocations很容易判断出当前链接对象所持有的StreamAllocation数目,该数组的大小用来判定一个链接的负载量是否超过指定的次数。

通过前面几篇关于Okhttp的的分析我们知道,每一次http请求会在RetryAndFollowUpInterceptor这个拦截器里创建一个StreamAllocation对象,或者简单的说每一次http请求都会产生一个StreamAllocation对象。这样说来如果若干个请求从链接池中获取的是同一的RealConnection对象,那么此时他们的关系可以简单的如下图所示:

从上图其实也可以发现说每次请求生成StreamAllocation对象请求链接时,首先要做的不是new 一个新的RealConnection对象,而是从链接池中获取已经存在的并且可以复用的RealConnection,如果找不到可用的链接,则才new 一个新的RealConnection(当然要把新创建的RealConnection放入链接池,以待别的请求复用之)。

上面简单的描述了下怎么从连接池获取连接,至于怎么判断链接池的某个连接是否可以复用,就是isEligible的作用了。在分析这个方法之前,先简单说下RealConnection:

RealConnection是Okhttp正式发起网络请求所使用的对象,该对象含有了一个通过RouteSelector选中Route对象,route对象正好持有了当前请求所访问的目标主机信息(InetSocketAddress)。事实上在上面所讲的findConnection方法中就是如果在ConnectionPool中找不到可用链接就根据选中的路由链初始化一个RealConnection交给StreamAllocation使用,当然此时会把新创建的链接放入ConnectionPool中,要不然链接池中的链接对象从何而来(这句算是没有bug的废话):

result = new RealConnection(connectionPool, selectedRoute);
//放入链接池
 Internal.instance.put(connectionPool, result);
return result

那么到底是如何判断一个RealConnection是否可以让StreamAllocation使用呢?其实一个链接能否复用的条件我们能想到无外乎地址一样、端口一样、一条链接线路的负载不超过指定负载数等等这些条件。Okhttp对复用条件做了更多的限制,详见isEligible代码:

public boolean isEligible(Address address, @Nullable Route route) {
    //1、负载超过指定最大负载,不可复用
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    //2、Address对象的非主机部分不相等,不可复用
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    //3、非主机部分不相等,不可复用
 if(address.url().host().equals(this.route().address().url().host())) {
     //这个链接完美的匹配
      return true; // This connection is a perfect match.
    }

    //
    // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
    // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

    // 4. This connection must be HTTP/2.
    if (http2Connection == null) return false;

    // The routes must share an IP address. This requires us to have a DNS address for both
    // hosts, which only happens after route planning. We can't coalesce connections that use a
    // proxy, since proxies don't tell us the origin server's IP address.
    //5
    if (route == null) return false;
    //6
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    //7
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    //8
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    // 9
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    // 10. Certificate pinning must match the host.
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }
    //最终可以复用
    return true;
  }

上面的代码可以发现基本上可以分成两大部分:

1、用StreamAllocation携带的Address对象跟RealConnection的Address相匹配;2、1不满足的情况下用StreamAllocation携带的Route(由RouteSelecor选择而来)与RealConnection的Route相匹配(具体的说是SocketAddress所表的信息)。

结合本文开头说的获取链接两次调用get方法的作用,StreamAllocation获取连接池中的链接的工作方式可以简单的如下表示:

现在为止再来分析分析isEligible的判定一个连接是否可复用的标准吧(对照上面源码的1、2、3.。。10的注释来说明):

1、如果此链接的负载数目超过指定数目(表现为RealConnection的allocations集合的数量超过该链接指定的数量)或者noNewStreams为true时,此链接不可复用。

2、StreamAllocation 所持有的Address对象和RealConnection的Address非主机部分不同,则此链接不可复用。至于非主机部分的判定是在Address的equalsNonHost方法来体现,代码如下:

boolean equalsNonHost(Address that) {
    return this.dns.equals(that.dns)
        && this.proxyAuthenticator.equals(that.proxyAuthenticator)
        && this.protocols.equals(that.protocols)
        && this.connectionSpecs.equals(that.connectionSpecs)
        && this.proxySelector.equals(that.proxySelector)
        && equal(this.proxy, that.proxy)
        && equal(this.sslSocketFactory, that.sslSocketFactory)
        && equal(this.hostnameVerifier, that.hostnameVerifier)
        && equal(this.certificatePinner, that.certificatePinner)
        && this.url().port() == that.url().port();
  }

两者Adress对象的非主机部分相等的标准就是dns,Authenticator对象、协议、CA授权验证标准、端口等信息全部相等。

3、在1、2判定条件都为true的话,如果两个Address对象的host或者说url中的host一样,则此链接可复用,正如注释说说,添加1、2、3都满足的话,那么此时这个链接就是This connection is a perfect match。

以上三点是Address对象的比较,如果步骤三能成功的话(地址的主机部分和非主机部分都一样),则就不需要route对象进行匹配验证了。否则则需要用route做进一步的验证来判断此链接是否可以复用。既然主机名都不想等了怎么还有复用的可能呢?由于牵扯到Http2等概念及博客篇幅问题,这个分析将在下一篇中详细分析,敬请期待《Okhttp之连接池ConnectionPool简单分析(二)》,如有不当之处,欢迎批评指正。

Okhttp之连接池ConnectionPool简单分析(一)的更多相关文章

  1. Okhttp对http2的支持简单分析

    在< Okhttp之RealConnection建立链接简单分析>一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开t ...

  2. Okhttp之RealConnection建立链接简单分析

    在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...

  3. DBCP数据源连接池实现原理分析

    前些天在调试公司系统的时候发现这样的一个问题:mysql数据库服务停止一段时间后再次重启后吗,tomcat服务无法请求数据库服务,调试了半天对这个问题进行定位解决,期间也搞了很多有关mysql数据库的 ...

  4. TCP连接与OKHTTP复用连接池

    Android网络编程(八)源码解析OkHttp后篇[复用连接池] 1.引子 在了解OkHttp的复用连接池之前,我们首先要了解几个概念. TCP三次握手 通常我们进行HTTP连接网络的时候我们会进行 ...

  5. java 连接池的简单实现

    最近一个项目中需要自己写个连接池, 写了一个下午,挺辛苦的,但不知道会不会出问题, 所以,贴到博客上,欢迎各路大神指点 1. 配置信息: /** * */ package cn.mjorcen.db. ...

  6. ActiveMQ学习心得:连接池的简单实现和模板模式的应用

    一.安装activemq 下载地址:https://archive.apache.org/dist/activemq/5.13.0/apache-activemq-5.13.0-bin.zip 下载完 ...

  7. java学习笔记—c3p0连接池与元数据分析(42)

    第一步:导入c3p0包 第二步:在classpath目录下,创建一个c3p0-config.xml <?xml version="1.0" encoding="UT ...

  8. 阿里巴巴连接池Druid简单使用

    Druid参考:https://github.com/alibaba/druid 偶尔的机会解释Druid连接池,后起之秀,但是评价不错,另外由于是阿里淘宝使用过的所以还是蛮看好的. Druid集连接 ...

  9. MySQL_(Java)【连接池】简单在JDBCUtils.java中创建连接池

    MySQL_(Java)[事物操作]使用JDBC模拟银行转账向数据库发起修改请求 传送门 MySQL_(Java)[连接池]使用DBCP简单模拟银行转账事物 传送门 Java应用程序访问数据库的过程: ...

随机推荐

  1. 【前端】Flex 布局教程:语法篇 [转]

    网页布局(layout)是 CSS 的一个重点应用. 布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂 ...

  2. git基础常用维护命令

    开发模式介绍 master为生产环境分支 trunk为测试环境分支 开发分支由程序员自己取名 比如来一个新项目之后,下面步骤都是在本地操作 1.从本地获取远程master最新代码,保证本地master ...

  3. DataReader 连接数据库完整过程和代码(Sql Server)

    数据库名叫:Bu 有个表:A 里面有一列:ID 需要引用 using System.Data.SqlClient; 代码部分如下: SqlConnection sqlCon=new SqlConnec ...

  4. LA 3523 圆桌骑士(二分图染色+点双连通分量)

    https://vjudge.net/problem/UVALive-3523 题意: 有n个骑士经常举行圆桌会议,商讨大事.每次圆桌会议至少应有3个骑士参加,且相互憎恨的骑士不能坐在圆桌旁的相邻位置 ...

  5. 《Think in Java》(十七)容器深入研究

    阿西吧,这一章好长啊,感觉看了快一个月了吧!JDK 自带的容器框架真是很好很强大啊,这一章看得有点蒙蒙的,接下来还得去看看官方文档啊!

  6. python脚本8_打印对顶三角形

    #打印对顶三角形 a = int(input('>>>')) for i in range(-a,a+1): if i < 0: i = -i print(" &qu ...

  7. customs event

    // First create the event var myEvent = new CustomEvent("userLogin", { detail: { username: ...

  8. wireshark初学者使用

    介绍 Wireshark是一款网络封包分析软件,截取网络封包,显示其封包的详细信息.日常工作中用的比较多.在使用wireshark之前须了解常用的网络协议.如:tcp,http,ip,udp等.(其实 ...

  9. idea maven install时,打包找不到微服务common中公用的包

    如题:其实很简单,在打包之前要先使项目通过编译,编译通过之后再打包就可以了. 附idea编译键:

  10. nyoj1007——欧拉求和

    GCD 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 The greatest common divisor GCD(a,b) of two positive in ...