Okhttp之连接池ConnectionPool简单分析(一)
开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的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简单分析(一)的更多相关文章
- Okhttp对http2的支持简单分析
在< Okhttp之RealConnection建立链接简单分析>一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开t ...
- Okhttp之RealConnection建立链接简单分析
在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...
- DBCP数据源连接池实现原理分析
前些天在调试公司系统的时候发现这样的一个问题:mysql数据库服务停止一段时间后再次重启后吗,tomcat服务无法请求数据库服务,调试了半天对这个问题进行定位解决,期间也搞了很多有关mysql数据库的 ...
- TCP连接与OKHTTP复用连接池
Android网络编程(八)源码解析OkHttp后篇[复用连接池] 1.引子 在了解OkHttp的复用连接池之前,我们首先要了解几个概念. TCP三次握手 通常我们进行HTTP连接网络的时候我们会进行 ...
- java 连接池的简单实现
最近一个项目中需要自己写个连接池, 写了一个下午,挺辛苦的,但不知道会不会出问题, 所以,贴到博客上,欢迎各路大神指点 1. 配置信息: /** * */ package cn.mjorcen.db. ...
- ActiveMQ学习心得:连接池的简单实现和模板模式的应用
一.安装activemq 下载地址:https://archive.apache.org/dist/activemq/5.13.0/apache-activemq-5.13.0-bin.zip 下载完 ...
- java学习笔记—c3p0连接池与元数据分析(42)
第一步:导入c3p0包 第二步:在classpath目录下,创建一个c3p0-config.xml <?xml version="1.0" encoding="UT ...
- 阿里巴巴连接池Druid简单使用
Druid参考:https://github.com/alibaba/druid 偶尔的机会解释Druid连接池,后起之秀,但是评价不错,另外由于是阿里淘宝使用过的所以还是蛮看好的. Druid集连接 ...
- MySQL_(Java)【连接池】简单在JDBCUtils.java中创建连接池
MySQL_(Java)[事物操作]使用JDBC模拟银行转账向数据库发起修改请求 传送门 MySQL_(Java)[连接池]使用DBCP简单模拟银行转账事物 传送门 Java应用程序访问数据库的过程: ...
随机推荐
- 20145324 《Java程序设计》第9周学习总结
20145324 <Java程序设计>第9周学习总结 教材学习内容总结 第十六章 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作 ...
- Linux简单编程学习心得
在Linux环境下简单编程学习心得 linux编程过程 在上周的<信息安全设计基础>的课程学习中学习到了在虚拟的linux环境下简单的编程.学习过程中接触到了vim.gcc和gcd在实验楼 ...
- LeetCode——Longest Word in Dictionary through Deleting
1. Question Given a string and a string dictionary, find the longest string in the dictionary that c ...
- 解题报告:hdu2191汶川地震 - 多重背包模板
2017-09-03 17:01:36 writer:pprp 这是一道多重背包裸题 - 记得是从右向左进行,还有几点需要注意啊,都在代码中表示出来了 代码如下: /* @theme:hdu2191 ...
- python中的参数传递
一般的参数顺序是先位置,再关键字,然后是包裹位置传递,包裹关键字传递.
- codeforces291E Tree-String Problem
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- 推荐给开发者的11个PHP框架(转)
PHP框架对于Web开发者来说是非常有用的工具,它可以帮助使用者更快.更容易的完成项目.根据调查,PHP仍是Web开发中最受欢迎和最实用的平台之一.当谈及Web开发时,很多人依然会选择使用PHP框架, ...
- Mahout 0.10.1安装(Hadoop2.6.0)及Kmeans测试
1.版本和安装路径 Ubuntu 14.04 Mahout_Home=/opt/mahout-0.10.1 Hadoop_Home=/usr/local/hadoop Mavent_Home=/opt ...
- 1-26-1-expect无交互式-正则表达式
大纲: 1.expect环境搭建及脚本编写 概述 expect脚本详解 expect环境搭建 expect脚本实现ssh远程连接 expect脚本实现ssh远程连接(通过shell传递参数) 2.正则 ...
- UVA-10537 The Toll! Revisited (dijkstra)
题目大意:每经过一个地方就要交出相应的货物作为过路费,问将一批货物从起点运到终点,最少需要携带多少货物? 题目分析:在每一站交的过路费由当前拥有的货物量来决定,所以,要以终点为源点,求一次单源最短路即 ...