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应用程序访问数据库的过程: ...
随机推荐
- 20145310 《Java程序设计》第8周学习总结
20145310 <Java程序设计>第8周学习总结 教材学习内容总结 本周主要进行第十四章和第十五章的学习. 第十四章 NIO使用频道(channel)来衔接数据节点,对数据区的标记提供 ...
- JAVA基本常识及环境搭建
JAVA基本常识及环境搭建 常用dos命令行 dir 列出当前目录下的文件以及文件夹 md 创建目录 cd 进入指定目录 cd.. 退回到上一级目录 cd/ 退回到根目录 del 删除文件 删除单个文 ...
- 20145328 《Java程序设计》实验三实验报告
20145328 <Java程序设计>实验三实验报告 实验名称 Java敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验步骤 (一)敏捷开发与XP 敏捷开发是一种以人为 ...
- String被设计成不可变和不能被继承的原因
String是所有语言中最常用的一个类.我们知道在Java中,String是不可变的.final的.Java在运行时也保存了一个字符串池(String pool),这使得String成为了一个特别的类 ...
- python x[:] x[::]用法总结
python x[:] x[::]用法总结 X[:,0] # 二维数组取第1维所有数据 X[:,1] # 第2列 X[0,:] # 第1行 X[3,:] # 第三行 X[1:4,:] # 第一二三行 ...
- codeforce 35C fire again
2017-08-25 17:04:07 writer:pprp 题目描述: • Codeforces 35C Fire Again• N*M的格子,最开始有K个点 (坐标给定) 开始着火• 每一秒着火 ...
- Vjudge - F - 比前面更简单的模拟
2017-07-16 07:31:35 writer:pprp 题目介绍:很基础的string用法 题目如下: 读入一个字符串,字符串中包含ZOJ三个字符,个数不一定相等,按ZOJ的顺序输出,当某个字 ...
- java如何实现Socket的长连接和短连接
讨论Socket必讨论长连接和短连接 一.长连接和短连接的概念 1.长连接与短连接的概念:前者是整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接:后者是每次请求,都新建 ...
- css hover dropdown
html-------------------------- <div class="dropdown"> <span>鼠标移动到我这!</span& ...
- SpringBoot学习(2)
三.日志 1.日志框架 springboot:底层是spring框架,spring框架默认使用JCL; springboot选用SLF4j和logback; 2.SLF4j使用 1.如何在系统中使用S ...