根据Apache HttpClient 4.5.6讲解。

HttpClient使用如下方式创建HttpClient时,在build()中会默认给HttpClient设置一个连接池PoolingHttpClientConnectionManager

return HttpClientBuilder.create().build();

默认创建的HttpClient是默认保持连接的keepalive。

我们使用HttpClient请求第三方服务器时,如果请求的是同一个服务器的地址是可以提高访问效率的。这是由于HTTP协议的底层是TCP,TCP建立连接的时候需要三次握手。如果使用连接池并keeplive是true,那么就是一个长链接,在第一次握手成功后,后面的请求就都不需要握手了,直接请求数据,这样就加快了访问速度。

我们一般获取到返回结果后都会做一些资源关闭的操作,HttpClient的executor方法也会默认做response的关闭操作。比如:

CloseableHttpResponse response = this.execute(target, request, context);
....
finally {
            response.close();
        }

那有没有想过这样关闭会对我上面说的长连接有影响吗?

如果是长连接这样关闭是没有影响的,它还是在连接池中。close()最后会调用下面这个方法。在finally中的close传入的参数是false,也就是会释放掉链接,但是它并不会调用成功。因为在前面已经执行过一次releaseConnection方法了(releaseConnection方法只能调用一次)。在response放回处理返回的结果的时候会调用close方法。那里的参数使用的是ConnectionHolder类的reusable成员变量。所以如果前面的程序执行成功最后的finally中的close方法到releaseConnection方法的CAS时会执行失败。

private void releaseConnection(boolean reusable) {
        if (this.released.compareAndSet(false, true)) {
            HttpClientConnection var2 = this.managedConn;
            synchronized(this.managedConn) {
                if (reusable) {
                    this.manager.releaseConnection(this.managedConn, this.state, this.validDuration, this.tunit);
                } else {
                    try {
                        this.managedConn.close();
                        this.log.debug("Connection discarded");
                    } catch (IOException var9) {
                        if (this.log.isDebugEnabled()) {
                            this.log.debug(var9.getMessage(), var9);
                        }
                    } finally {
                        this.manager.releaseConnection(this.managedConn, (Object)null, 0L, TimeUnit.MILLISECONDS);
                    }
                }
            }
        }

    }

可以看到是通过reusable去判断的。这个在CloseableHttpResponse构造的时候就已经设定了。

if (reuseStrategy.keepAlive(response, context)) {
                    // Set the idle duration of this connection
                    final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                    if (this.log.isDebugEnabled()) {
                        final String s;
                        if (duration > 0) {
                            s = "for " + duration + " " + TimeUnit.MILLISECONDS;
                        } else {
                            s = "indefinitely";
                        }
                        this.log.debug("Connection can be kept alive " + s);
                    }
                    connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
                    connHolder.markReusable();
                } else {
                    connHolder.markNonReusable();
                }
------
public void markReusable() {
        this.reusable = true;
    }

----
 public void markNonReusable() {
        this.reusable = false;
    }

看这个大概就可以看出,就是通过keepAlive去判断的。如果是需要保持长连接的就将reusable设定为true,否则会设定为false。

回到前面的releaseConnection(),它没有去关闭socket,而是将连接放入了一个链表供后面使用。然后再执行的时候去这个链表中获取连接。这个链表是RouteSpecificPool类available成员变量。获取连接使用的是getFree方法。

前面我们讨论了使用一个httpClient请求一个地址的时候可以使用长连接来优化请求速度。如果这个httpClent会被用来访问多个地址呢(一般都是会有一个专门的HTTP请求工具类)?

这个可以放心的使用,该有的长连接还是会继续有(没有超时的)。下次访问还是会用到这个长连接。这是因为HttpClient会根据不同的请求地址做不同的缓存。具体可以看org.apache.http.pool.AbstractConnPool的设计。

以上。