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时,认为是可用的。而这时候,很有可能连接不可用,比如服务端关闭链接的情况。

出处:https://www.cnblogs.com/shoren/p/httpclient-leaseConnection.html

其他: https://blog.csdn.net/u013905744/article/details/94714696

https://stackoverflow.com/questions/13837012/spring-resttemplate-timeout

https://www.cnblogs.com/coderjinjian/p/9644923.html

https://www.cnblogs.com/shoren/p/httpclient-leaseConnection.html

https://www.cnblogs.com/shoren/p/http-connection.html

https://blog.csdn.net/u014133299/article/details/80676147

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-resttemplate

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. CS5212Capstone|DP转VGA方案设计芯片|CS5212设计资料

    CS5212是台湾CAPSTONE最新开发出一款用于DP转VGA方案设计芯片,其周围器件较少,设计简单,整体BOM成本低,市场性价比较高. CS5212内置MCU是工业标准8051核心单片机处理器.C ...

  2. AVD Pixel_2_API_30 is already running. lf that is not the case, delete the files at

    AVD Pixel_2_API_30 is already running. lf that is not the case, delete the files at C:\Users\Adminis ...

  3. CentOS8.2安装docker

    1:安装docker前准备docker配置仓库(安装yum-utils是提供一个yum-config-manager单元,同时安装的device-mapper-persistent-data和lvm2 ...

  4. js获取设备内网ip

    可以直接使用,不需要导入其他配置 看代码 1 <script> 2 //获取内网ip 3 var RTCPeerConnection = window.RTCPeerConnection ...

  5. ajax 异步 提交 含文件的表单

    1.前言 需求是使用 jquery 的 ajax 异步提交表单,当然,不是简单的数据,而是包含文件数据的表单.于是我想到了 new FormData() 的用法, 可是仍然提交失败,原来是ajax的属 ...

  6. 苹果系统 的 qq浏览器 和 qq内置浏览器 无法使用 websocket 的 妥协方案

    没错,就是用不了,js脚本不执行,更别说服务器运行 onopen函数了!!! 怎么办...搞了一天,仍然找不到连接的方法!!! 幸运的是仅仅苹果系统 的无法使用 ,安卓的却可以,奇了怪了 哈皮 ,那我 ...

  7. nginx代理图片上传以及访问 nginx 图片上传完整版

    nginx代理图片上传 首先需要利用nginx代理图片访问参考 https://www.cnblogs.com/TJ21/p/12609017.html 编写接受文件的controller 1 @Po ...

  8. 深入浅出 CSS 动画

    本文将比较全面细致的梳理一下 CSS 动画的方方面面,针对每个属性用法的讲解及进阶用法的示意,希望能成为一个比较好的从入门到进阶的教程. CSS 动画介绍及语法 首先,我们来简单介绍一下 CSS 动画 ...

  9. Appium+python自动化测试过程中问题

    一.自动删除contactmanager 自动化测试appium提供的sample如下包/activity:com.example.android.contactmanager/.ContactMan ...

  10. Android-ViewPager中调用notifyDataSetChanged失效问题--setItemPosition--POSITION_NONE

    最基本的方法: 针对于child view比较简单的情况(例如仅有TextView.ImageView等,没有ListView等展示数据的情况),可以在自己的Adapter中加入代码: @Overri ...