开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的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. 20145329《Java程序设计》第八周学习总结

    教材学习内容总结 日志 1.java.util.logging包提供了日志功能相关类与接口. 2.使用日志的起点是Logger类,Longer类的构造函数标示为protected,不同包的类药取得Lo ...

  2. @component的注解

    1.@controller 控制器(注入服务) 2.@service 服务(注入dao) 3.@repository dao(实现dao访问) 4.@component (把普通pojo实例化到spr ...

  3. The Startup Manager FAQ

    Main Features: 1.  Login Items: Manageable list of applications that are launched automatically ever ...

  4. intellij 文件太大,无法code assistant

    添加 idea.max.intellisense.filesize=2500 在IDE_HOME\bin\idea.properties https://intellij-support.jetbra ...

  5. 爬虫之动态HTML处理(Selenium与PhantomJS )

    Selenium Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动操作,不同是Selenium 可以直接运行在浏览器上, ...

  6. Docker 坑点记录

    1 关于 Docker Windows 文件夹问题 C:\Users Docker Machine tries to auto-share your /Users (OS X) or C:\Users ...

  7. helloworld:一个完整的WCF案例

    服务端 1.创建一个空的解决方案:WCFDemo: 2.创建一个宿主控制台程序:Host 3.右击Host项目,选择“添加”--“新建项”,选择“WCF服务”创建名为“Service1.cs”的服务 ...

  8. 深入理解AUC

    https://tracholar.github.io/machine-learning/2018/01/26/auc.html 我觉得作者写的很不错

  9. poj2187凸包最远点对

    暴力过了 #include<map> #include<set> #include<cmath> #include<queue> #include< ...

  10. 启用/禁用以太网的批处理,用于一个网卡切换本地网络和wifi使用(Win10)

    注意下面时英文版上默认网络使用,同时接入了网线和wifi时,本地网络优先wifi. 所以禁用本地网络就会自动连接到wifi,启用本地网络,就会禁用wifi. 批处理支持 -y 参数,跳过用户输入y,代 ...