PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务。主要作用就是分配连接,回收连接等。同一个route的请求,会优先使用连接池提供的空闲长连接。

源码版本4.5.2,因为代码太多,很多不是自己关心的,为免看起来费力,这里代码贴的不全。省略代码的地方用省略号标注。

配置说明

    <bean id="ky.pollingConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
<!--整个连接池的最大连接数 -->
<property name="maxTotal" value="1000" />
<!--每个route默认的连接数-->
<property name="defaultMaxPerRoute" value="32" />
</bean>
  • maxTotal 是整个连接池的最大连接数
  • defaultMaxPerRoute 是每个route默认的最大连接数
  • setMaxPerRoute(final HttpRoute route, final int max) route的最大连接数,优先于defaultMaxPerRoute。
    public PoolingHttpClientConnectionManager(
final HttpClientConnectionOperator httpClientConnectionOperator,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
final long timeToLive, final TimeUnit tunit) {
super();
this.configData = new ConfigData();
//defaultMaxPerRoute默认为2,maxTotal默认为20
this.pool = new CPool(new InternalConnectionFactory(
this.configData, connFactory), 2, 20, timeToLive, tunit);
//validateAfterInactivity 空闲永久连接检查间隔,这个牵扯的还比较多
//官方推荐使用这个来检查永久链接的可用性,而不推荐每次请求的时候才去检查
this.pool.setValidateAfterInactivity(2000);
this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");
this.isShutDown = new AtomicBoolean(false);
}

获取连接

获取连接分两步,首先新建一个ConnectionRequest,在通过request.get得到HttpClientConnection。

    //org.apache.http.impl.conn.PoolingHttpClientConnectionManager

    @Override
public ConnectionRequest requestConnection(
final HttpRoute route,
final Object state) {
...... //从连接池中获取一个CPoolEntry(Connection的包装类)
final Future<CPoolEntry> future = this.pool.lease(route, state, null);
return new ConnectionRequest() {
...... // ConnectionRequest的get方法。调用leaseConnection方法,并且传入future(CPoolEntry的封装(connection的封装))
@Override
public HttpClientConnection get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
return leaseConnection(future, timeout, tunit);
}
};
} protected HttpClientConnection leaseConnection(
final Future<CPoolEntry> future,
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
final CPoolEntry entry;
try {
//从future中get
entry = future.get(timeout, tunit);
if (entry == null || future.isCancelled()) {
throw new InterruptedException();
}
Asserts.check(entry.getConnection() != null, "Pool entry with no connection"); //封装返回
return CPoolProxy.newProxy(entry);
} catch (final TimeoutException ex) {
throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
}
}

所以,CPoolEntry(ManagedHttpClientConnection的封装),实际是调用PoolingHttpClientConnectionManager的leaseConnection,通过future的get获得。

这里的future是Future future = this.pool.lease(route, state, null);得到的。

    //org.apache.http.pool.AbstractConnPool
@Override
public Future<E> lease(final T route, final Object state, final FutureCallback<E> callback) {
...... return new PoolEntryFuture<E>(this.lock, callback) {
@Override
public E getPoolEntry(
final long timeout,
final TimeUnit tunit)
throws InterruptedException, TimeoutException, IOException {
//阻塞获取CPoolEntry
final E entry = getPoolEntryBlocking(route, state, timeout, tunit, this);
onLease(entry);
return entry;
} };
} private E getPoolEntryBlocking(
final T route, final Object state,
final long timeout, final TimeUnit tunit,
final PoolEntryFuture<E> future)
throws IOException, InterruptedException, TimeoutException {
//设置超时时间点
...... //串行操作
this.lock.lock();
try {
//每一个route都有一个连接池,这里获取指定route的连接池
final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry = null;
//循环取,直到超时
while (entry == null) {
Asserts.check(!this.isShutDown, "Connection pool shut down");
for (;;) {
//从连接池中去一个空闲的连接,优先取state相同的。state默认是null
entry = pool.getFree(state);
//如果没有符合的连接,则调出,创建一个新连接
if (entry == null) {
break;
}
//如果连接超时,则关闭
if (entry.isExpired(System.currentTimeMillis())) {
entry.close();
//如果是永久连接,且最近周期内没有检验,则校验连接是否可用。不可用的连接需要关闭
} else if (this.validateAfterInactivity > 0) {
if (entry.getUpdated() + this.validateAfterInactivity <= System.currentTimeMillis()) {
if (!validate(entry)) {
entry.close();
}
}
}
//如果连接已经关闭了,则释放掉,继续从池子中取符合条件的连接
if (entry.isClosed()) {
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
//entry不为空,则修改连接池的参数,并返回。
if (entry != null) {
this.available.remove(entry);
this.leased.add(entry);
onReuse(entry);
return entry;
} // New connection is needed
//获取池子的最大连接数,如果池子已经超过容量了,需要把超过的资源回收
//如果池子中连接数没有超,空闲的连接还比较多,就先从别人的池子里借一个来用
...... //不能借,就自己动手了。新建并返回。
final C conn = this.connFactory.create(route);
entry = pool.add(conn);
this.leased.add(entry);
return entry;
}
throw new TimeoutException("Timeout waiting for connection");
} finally {
this.lock.unlock();
}
}

读到这里,看起来拿到的entry要么是刚刚创建的热乎的,要么是没有过期的连接,要么是复用的池子中有效的永久连接。是这样的吗?再看一下复用的永久连接的情况:

//如果往前validateAfterInactivity ms之内没有校验,则校验entry。校验不通过则关闭并释放,继续从连接池中获取entry。
//如果往前validateAfterInactivity ms之内有过校验,则无需再次校验
if (entry.getUpdated() + this.validateAfterInactivity <= System.currentTimeMillis()) {
if (!validate(entry)) {
entry.close();
}
}

判断连接是否可用:

	//org.apache.http.impl.conn.CPool
protected boolean validate(final CPoolEntry entry) {
return !entry.getConnection().isStale();
} //org.apache.http.impl.BHttpConnectionBase
//判断连接是否不可用(go down)
public boolean isStale() {
//没有打开,即socket为空,则不可用
if (!isOpen()) {
return true;
}
try {
//socket链路有了,测试链路是否可用
//这里的测试方法是查看很短的时间内(这里是1ms),是否可以从输入流中读到数据
//如果测试结果返回-1说明不可用
final int bytesRead = fillInputBuffer(1);
return bytesRead < 0;
} catch (final SocketTimeoutException ex) {
//注意这里SocketTimeoutException时,认为是可用的
return false;
} catch (final IOException ex) {
//有I/O异常,不可用
return true;
}
}

了解下测试连接是否可用的过程,梳理一下调用链路:

  • org.apache.http.impl.BHttpConnectionBase

    private int fillInputBuffer(final int timeout) throws IOException 不处理异常

  • org.apache.http.impl.io.SessionInputBufferImpl

    public int fillBuffer() throws IOException 不处理异常

  • org.apache.http.impl.conn.LoggingInputStream

    打印日志,不处理异常

    @Override
public int read(final byte[] b, final int off, final int len) throws IOException {
try {
final int bytesRead = in.read(b, off, len);
if (bytesRead == -1) {
wire.input("end of stream");
} else if (bytesRead > 0) {
wire.input(b, off, bytesRead);
}
return bytesRead;
} catch (final IOException ex) {
wire.input("[read] I/O error: " + ex.getMessage());
throw ex;
}
}
  • sun.security.ssl.AppInputStream

    public synchronized int read(byte[] var1, int var2, int var3) throws IOException 不处理异常

  • sun.security.ssl.SSLSocketImpl

    void readDataRecord(InputRecord var1) throws IOException 不处理异常

  • sun.security.ssl.InputRecord

    void read(InputStream var1, OutputStream var2) throws IOException

  • java.net.SocketInputStream

    int read(byte b[], int off, int length, int timeout) throws IOException

    通过socket读取数据,如果发生ConnectionResetException异常,则throw new SocketException("Connection reset");

以上,是对PoolingHttpClientConnectionManager从连接池中获取一个连接给用户的过程。用户拿到的连接有三种:新创建的;未过期的短连接;间隔检查的永久链接。

需要注意,间隔检查的永久链接 如果在间隔时间(这里是2s)内,socket连接出现什么问题,是不知道的,因为没有进行检测。另外,检查链接是否可用的方法 isStale ,并不是100%靠谱的,即检测时出现SocketTimeoutException时,认为是可用的。而这时候,很有可能连接不可用,比如服务端关闭链接的情况。

httpclient源码分析之 PoolingHttpClientConnectionManager 获取连接的更多相关文章

  1. httpclient源码分析之 PoolingHttpClientConnectionManager 获取连接 (转)

    PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务.主要作用就是分配连接,回收连接等.同一个rou ...

  2. springMVC源码分析--DispatcherServlet请求获取及处理

    在之前的博客springMVC源码分析--容器初始化(二)DispatcherServlet中我们介绍过DispatcherServlet,是在容器初始化过程中出现的,我们之前也说过Dispatche ...

  3. HDFS源码分析EditLog之获取编辑日志输入流

    在<HDFS源码分析之EditLogTailer>一文中,我们详细了解了编辑日志跟踪器EditLogTailer的实现,介绍了其内部编辑日志追踪线程EditLogTailerThread的 ...

  4. httpclient源码分析之MainClientExec

    MainClientExec是HTTP请求处理链中最后一个请求执行环节,负责与另一终端的请求/响应交互,也是很重要的类. 源码版本是4.5.2,主要看execute方法,并在里面添加注释.接着详细说下 ...

  5. rocketmq源码分析3-consumer消息获取

    使用rocketmq的大体消息发送过程如下: 在前面已经分析过MQ的broker接收生产者客户端发过来的消息的过程,此文主要讲述订阅者获取消息的过程,或者说broker是怎样将消息传递给消费者客户端的 ...

  6. openfalcon源码分析之transfer

    本节内容 transfer功能 transfer接收数据来源 transfer数据去向 transfer的一致性hash transfer的一致性hash key的计算 transfer源码分析 2. ...

  7. ASimpleCache源码分析

    ASimpleCache里只有一个JAVA文件——ACache.java,首先我用思维导图制作了ACache类的详细结构图: 通过分析官方给的demo来驱动源码分析吧 以字符串存储为例(官方给的dem ...

  8. ASP.NET MVC源码分析

    MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...

  9. Tomcat 源码分析(转)

    本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...

随机推荐

  1. 安卓UDP通信2

    服务器实现一发一收 服务器代码: import java.net.*; import java.io.*; public class udpRecv2 { /* * 创建UDP传输的接收端 * 1.建 ...

  2. IP数据报格式 及路由转发算法

    ip数据报分首部和数据两部分组成: 首部分为固定部分和可变部分 版本--占 4 位,指 IP 协议的版本 目前的 IP 协议版本号为 4 (即 IPv4) 首部长度--占 4 位,可表示的最大数值 是 ...

  3. angular ng-repeat数组中的数组

    //先定义一个数组anular代码: var app = angular.module('serApp', []); app.controller('indexCtrl', function($sco ...

  4. object c入门

    无意间看到Object C编写的程序,感觉蛮有意思的,记载下来,慢慢品味,也许会有用得上的时候.吼吼~~ 大部分有一点其他平台开发基础的初学者看到XCode,第一感想是磨拳擦掌,看到 Interfac ...

  5. C++程序员的阅读清单

    link:http://www.who1753.com/must-read-c-book-list/ 多读一些优秀的书籍,对于开发者稳固编程基础.提高编程技能有很大帮助.但是,大多时候,初学者不知道应 ...

  6. JavaScript 基本类型值-String类型

    ▓▓▓▓▓▓ 大致介绍 String类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串.在JavaScript中没有单个的字符型,都是字符串.字符型就相当于只包含一个字符的字符串. ...

  7. XHTML清单

    1.无序清单 <ul> <li>...</li> <li>...</li> <li>...</li> </ul ...

  8. 强化学习读书笔记 - 02 - 多臂老O虎O机问题

    # 强化学习读书笔记 - 02 - 多臂老O虎O机问题 学习笔记: [Reinforcement Learning: An Introduction, Richard S. Sutton and An ...

  9. 规范 : disable account

    前台的cookies在后台会去拿account出来,之后在filter status = disable的 用户在登入使用界面请求一个ajax,这时发现是401没有权限,这通常是admin把用户的ac ...

  10. 每天一个linux命令(56)--crontab命令

    上一节学习了 at  命令是针对仅运行一次的任务,循环运行的例行性计划任务,Linux 系统则是由 cron(crond)这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个 ...