在上一篇文章里我们主要介绍了 httpclient 连接池的关键类和数据结构,在这里我们主要介绍http连接的申请和释放。
http连接的申请
private E getPoolEntryBlocking(final T route, final Object state, final long timeout, final TimeUnit timeUnit, final Future<E> future) throws IOException, InterruptedException, TimeoutException {Date deadline = null;if (timeout > 0) {deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout));}this.lock.lock();try {final RouteSpecificPool<T, C, E> pool = getPool(route);E entry;for (;;) {Asserts.check(!this.isShutDown, "Connection pool shut down");for (;;) {entry = pool.getFree(state);if (entry == null) {break;}if (entry.isExpired(System.currentTimeMillis())) {entry.close();}if (entry.isClosed()) {this.available.remove(entry);pool.free(entry, false);} else {break;}}if (entry != null) {this.available.remove(entry);this.leased.add(entry);onReuse(entry);return entry;}// New connection is neededfinal int maxPerRoute = getMax(route);// Shrink the pool prior to allocating a new connectionfinal int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);if (excess > 0) {for (int i = 0; i < excess; i++) {final E lastUsed = pool.getLastUsed();if (lastUsed == null) {break;}lastUsed.close();this.available.remove(lastUsed);pool.remove(lastUsed);}}if (pool.getAllocatedCount() < maxPerRoute) {final int totalUsed = this.leased.size();final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);if (freeCapacity > 0) {final int totalAvailable = this.available.size();if (totalAvailable > freeCapacity - 1) {if (!this.available.isEmpty()) {final E lastUsed = this.available.removeLast();lastUsed.close();final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());otherpool.remove(lastUsed);}}final C conn = this.connFactory.create(route);entry = pool.add(conn);this.leased.add(entry);return entry;}}boolean success = false;try {if (future.isCancelled()) {throw new InterruptedException("Operation interrupted");}pool.queue(future);this.pending.add(future);if (deadline != null) {success = this.condition.awaitUntil(deadline);} else {this.condition.await();success = true;}if (future.isCancelled()) {throw new InterruptedException("Operation interrupted");}} finally {// In case of 'success', we were woken up by the// connection pool and should now have a connection// waiting for us, or else we're shutting down.// Just continue in the loop, both cases are checked.pool.unqueue(future);this.pending.remove(future);}// check for spurious wakeup vs. timeoutif (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {break;}}throw new TimeoutException("Timeout waiting for connection");} finally {this.lock.unlock();}}
对以上代码分析如下:
调用 getPool(route) 方法,根据当前 http 调用的 route 查找上一篇文章介绍的 individual 连接池对象 RouteSpecificPool ,如果没有就创建一个并加入到上一篇文章里 Cpool 对象中的 Map 里,以便后面可以重用。
在一个循环里尝试获取上一篇文章介绍的池化对象 CpoolEntry 。
在上述循环的子循环中调用连接池对象 pool.getFree() 方法尝试获取 CpoolEntry 对象。在 getFree() 方法里尝试从 individual 连接池对象的可用集合 available 里获取,如果有就从其中去掉,放入 individual leased 集合中,表示正在使用。如果没有就返回空值。
如果 pool.getFree() 方法调用能够直接返回可用的 CpoolEntry 对象实例,首先通过调用方法entry.isExpired() 检查是否过期,过期则通过 entry.close() 关闭连接。
entry.isExpired() 本质是检查过期时间和当前时间对比,过期时间由 global 连接池 Cpool 对象的timeToLive决定,timeToLive的值可以通过HttpClientBuilder.setConnectionTimeToLive() 方法设置,没有设置默认为Long.MAX_VALUE,单位是毫秒。
entry.close() 本质是把上一篇文章中介绍的 ManagedHttpClientConnectin 里绑定的 socket 关闭输入输出流,关闭socket,设置绑定socket为null。entry.isOpen() 和 entry.isClosed() 就是判断绑定的 socket 是否为 null ,核心代码如下: public void close() throws IOException {final Socket socket = this.socketHolder.getAndSet(null);if (socket != null) {try {this.inBuffer.clear();this.outbuffer.flush();try {try {socket.shutdownOutput();} catch (final IOException ignore) {}try {socket.shutdownInput();} catch (final IOException ignore) {}} catch (final UnsupportedOperationException ignore) {// if one isn't supported, the other one isn't either}} finally {socket.close();}}}public boolean isOpen() {return this.socketHolder.get() != null;}public boolean isClosed() {final HttpClientConnection conn = getConnection();return !conn.isOpen();}如果pool.getFree()方法调用返回可用连接CpoolEntry对象,通过entry.isClosed()检查其是否关闭,如果关闭则从 global Cpool 可用连接集合 available 里移除,并从 individual 连接池 RouteSpecificPool 正在使用的集合 leased 中移除。
如果 pool.getFree() 方法调用直接返回可用连接 CpoolEntry 对象在上面的检查步骤中即没有过期也没有关闭,则表示可用,那么就从 global Cpool 可用连接集合 available 中移除,并加入 global Cpool 的正在使用连接集合 leased 中,然后返回,结束上面步骤中的循环。
如果 pool.getFree() 调用返回为 null,表示没有可用连接,然后就会去检查当前是否超过 global Cpool 的限制,是否超过 individual 连接池的限制,如果没有则创建,并分别加入 global 连接池和 individual 连接池的正在使用集合 leased 里。然后返回,结束上面步骤中的循环。
如果上述步骤中已经超过了连接池的限制,那么把请求对象分别加入 global 连接池和 individual 连接池的请求集合 pending 里。然后利用对象锁,使当前线程在该锁上等待。等待时间由RequestConfig.Builder.setConnectionRequestTimeout() 方法决定,默认不设置值为 0 ,为 0 的时候当前线程无限等待。
上一步中超过了连接池的限制,则当前线程在该锁上等待,如果等待超时那么就意味申请可用连接失败,抛出异常TimeoutException("Timeout waiting for connection")。如果被其他线程唤醒,意味着有可用连接释放到池中,然后继续循环以上各个步骤尝试获取连接。
目前先写到这里,在下一篇文章里我们介绍 http 连接的释放。





