`
suichangkele
  • 浏览: 198065 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

HttpClient解读(3)-http连接的归还、关闭

    博客分类:
  • http
阅读更多

上一篇中主要说了借出后怎么使用,这一篇主要介绍使用完了如何归还,再次回到使用的部分的代码里面,MinimalClientExec.execute(HttpRoute, HttpRequestWrapper, HttpClientContext, HttpExecutionAware)中的最后几行

final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn);//用一个对象封装借出来的connection,方便复用
。。。省略很多不需要的
if (reuseStrategy.keepAlive(response, context)) {//如果是keepalive的则继续使用
	// Set the idle duration of this connection
	final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
	releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS);//设置校验前的截止时间
	releaseTrigger.markReusable();//标记为可以重复使用的
} else {
	releaseTrigger.markNonReusable();//如果不是1.1的keepalive,则标记为不可复用的。
}

// check for entity, release connection if possible
final HttpEntity entity = response.getEntity();
if (entity == null || !entity.isStreaming()) {//不管这种没有stream的,我们使用tomcat这种都是有stream的,我猜测这种是为了测试用的
	releaseTrigger.releaseConnection();
	return new HttpResponseProxy(response, null);
} else {
	return new HttpResponseProxy(response, releaseTrigger);//最后返回的一个response对象是一个代理,他里面封装了真正的response和releaseTrigger,也就自动包含了httpconnection
}

 看下下httpResponseProxy的代码

private final HttpResponse original;//包装的原始的httpResponse,
private final ConnectionHolder connHolder;//里面含有httpConnection,用于重用这个httpConnection。

public HttpResponseProxy(final HttpResponse original, final ConnectionHolder connHolder) {
	this.original = original;
	this.connHolder = connHolder;
	ResponseEntityProxy.enchance(original, connHolder);//将包装的原始的HTTPResponse进行加强,这个方法很重要
}

 上面的HttpResponseProxy仅仅是一个代理,我们在使用reponse的时候使用的是getEntity方法,使用的是getStatusLine方法,他都是调用的里面封装的original的方法,但是在调用getEntity.getContent的时候,也就是获得相应的具体的内容的时候就不是,因为上面的构造方法里面加强了original返回的Entity对象,看一下ResponseEntityProxy.enchance(HttpResponse, ConnectionHolder):

 

 

public static void enchance(final HttpResponse response, final ConnectionHolder connHolder) {
	final HttpEntity entity = response.getEntity();//这里的reponse就是上面的original,也就是最原始的结果
        if (entity != null && entity.isStreaming() && connHolder != null) {
		response.setEntity(new ResponseEntityProxy(entity, connHolder));//加强最原始的结果里面的entity,看下这个类ResponseEntityProxy,他也是一个Entity,重点看下他的getContent方法
	}
}
@Override
public InputStream getContent() throws IOException {
	return new EofSensorInputStream(this.wrappedEntity.getContent(), this);//返回的是一个对象,封装了最原始的inputStream,还有就是把自己传进去了,看一下这个EofSensorInputStream
}

 他的java注释上写着:他用于在close的时候触发操作,主要用于释放底层连接,他的构造方法里面有一个EofSensorWatcher,也就是监视器模式的应用,在eof(end of file)的时候会调用监视器。

这样当调用response.getEntity.getContent的时候就会返回一个EofSensorInputStream,看一下他的read方法:

@Override
public int read() throws IOException {
	int l = -1;
	if (isReadAllowed()) {
		try {
			l = wrappedStream.read();
			checkEOF(l);
		} catch (final IOException ex) {
			checkAbort();
			throw ex;
		}
	}
	return l;
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
	int l = -1;
	if (isReadAllowed()) {
		try {
			l = wrappedStream.read(b,  off,  len);
			checkEOF(l);
		} catch (final IOException ex) {
			checkAbort();throw ex;
		}
	}
	return l;
}
@Override
public int read(final byte[] b) throws IOException {
	return read(b, 0, b.length);
}

 可以发现他的read操作都是调用的封装的inputStream的方法,没有任何的加强,再看看他的close方法:

public void close() throws IOException {
	selfClosed = true;//表示当前的已经关闭了,
	checkClose();//通知监视器,调用他的相关方法
}
protected void checkClose() throws IOException {
	if (wrappedStream != null) {
		try {
			boolean scws = true; // should close wrapped stream?
			if (eofWatcher != null) {
				scws = eofWatcher.streamClosed(wrappedStream);//调用关闭方法
			}
			if (scws) {
				wrappedStream.close();
			}
		} finally {
			wrappedStream = null;
		}
	}
}

 之前穿进去的监视器就是ResponseEntityProxy,看看他的streamClosed方法:

public boolean streamClosed(final InputStream wrapped) throws IOException {
	try {
		final boolean open = connHolder != null && !connHolder.isReleased();//这里就是包装了httpConnection的对象,用于复用
		try {
			wrapped.close();
			releaseConnection();//这里可以发现在调用完了只有就会地哦啊用releaseConnection方法,
		} catch (final SocketException ex) {
			if (open) {throw ex;}
		}
	} finally {cleanup();}
	return false;
}
public void releaseConnection() throws IOException {
	if (this.connHolder != null) {
		try {
			if (this.connHolder.isReusable()) {//如果能复用,则调用release方法,这里的能复用就是一开始的是否是keepalive的判断,如果是则可以复用,
				this.connHolder.releaseConnection();//下面有这个方法:
			}
		} finally {
			cleanup();
		}
	}
}

ConnectionHolder.releaseConnection():

public void releaseConnection() {
	synchronized (this.managedConn) {
		if (this.released) {
			return;
		}
		this.released = true;
		if (this.reusable) {//如果是1.1的就可以复用
			this.manager.releaseConnection(this.managedConn,this.state, this.validDuration, this.tunit);//manager即之前穿进去的PoolingHttpClientConnectionManager,复用的话就要复用这个connection,还要更新他的校验时间
		} else {//如果不是,则关闭整个http连接管理器
			try {
				this.managedConn.close();
			} catch (final IOException ex) {
				if (this.log.isDebugEnabled()) {
					this.log.debug(ex.getMessage(), ex);
				}
			} finally {
				this.manager.releaseConnection(this.managedConn, null, 0, TimeUnit.MILLISECONDS);
			}
		}
	}
}

 上面经过了好几层代理,最开始是HttpResponseProxy代理了最底层的httpResponse,在代理的时候对最底层的HTTPResponse的entity进行了增强,增强后的entity的getContent方法返回的是一个可以监事关闭操作的inpustream,在关闭的时候会调用connectionHolder的释放连接的操作,释放连接就会放到缓存池,顺便更新校验时间。所以,现在只要关闭了最外层的inpustream,就会自动的释放连接,放回缓存池。还有PoolingHttpClientConnectionManager的释放连接方法没看:

public void releaseConnection(final HttpClientConnection managedConn,final Object state,final long keepalive, final TimeUnit tunit) {//第一个参数及要释放的connection,第三个是剩余的keepalive时间,第四个是时间单位
	synchronized (managedConn) {
		final CPoolEntry entry = CPoolProxy.detach(managedConn);//最早借出来的是CPoolEntry,然后再返回的时候是使用CPoolProxy代理了,现在是逆向操作。
		if (entry == null) {
			return;
		}
		final ManagedHttpClientConnection conn = entry.getConnection();//里面的connection
		try {
			if (conn.isOpen()) {
				final TimeUnit effectiveUnit = tunit != null ? tunit : TimeUnit.MILLISECONDS;
				entry.setState(state);
				entry.updateExpiry(keepalive, effectiveUnit);//更新失效时间
			}
		} finally {
			this.pool.release(entry, conn.isOpen() && entry.isRouteComplete());//继续调用CPool的release方法,将连接放回缓存池。
		}
	}
}

 CPool的释放连接的方法:

@Override
public void release(final E entry, final boolean reusable) {
	this.lock.lock();
	try {
		if (this.leased.remove(entry)) {//从借出的结合中删除
			final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());//找到对应的池子
			pool.free(entry, reusable);//添加进去
			if (reusable && !this.isShutDown) {//可以复用,则添加到可以复用的集合里面
				this.available.addFirst(entry);
				onRelease(entry);//回调函数,现在没有内容
			} else {
				entry.close();
			}
			PoolEntryFuture<E> future = pool.nextPending();//这个是唤醒正在等待的线程,现在还回来了,可以继续尝试获取了。
			if (future != null) {
				this.pending.remove(future);
			} else {
				future = this.pending.poll();
			}
			if (future != null) {
				future.wakeup();
			}
		}
	} finally {
		this.lock.unlock();
	}
}

这样,整个归还的流程就完了。 可以发现,在使用httpClient的时候只要调用最后的inputStream的close方法,借出去的连接会自动的还回到缓存池中,也就是使用EntityUtils.consume(entiry)就可以了。

 

 

上面看完了归还,还引发了另一个问题,就是尽管我们关闭了inpustream,但是其实socket底层并没有关闭,仅仅是将含有socket的connection换回到了连接池,还有就是那些过期的连接也是一直在连接池里面,如何关掉socket、关掉那些过期的socket呢?

对于在关闭程序时关闭所有的连接的需求,可以将整个httpClient作为spring的一个bean使用,然后再关闭容器的时候关闭httpClient,这样整个缓存池就会全部被关闭,对于那些在程序运行期间关闭那些过期的连接的需求(过期的连接在这些连接被获取的时候也是会检查的,但是在没有被检查到的时刻之前还是会造成资源浪费),可以打开一个配置,让后台自动检查过期的或者是长时间没有使用的连接(过期的连接则是超过了keepalive的时间,而长时间没有使用的是最后一次更新时间太久的连接),具体操作是:

HttpClientBuilder hcb = HttpClientBuilder.create();	
hcb.setConnectionManager(poolManager);//忽略
hcb.setDefaultRequestConfig(httpConfig);//忽略
hcb.evictExpiredConnections();//设置淘汰过期的连接
hcb.evictIdleConnections(maxIdleTime, maxIdleTimeUnit)//设置关闭空闲的连接,后面的参数制定了最大的空闲时间,开启这两个之后,就会启动一个定时任务,定时任务每隔maxIdleTime运行一次,调用AbstractConnPool.closeIdle(long, TimeUnit)和closeExpired方法。

 

好了,httpClient的源码就看到这里了,对于我们应用层的应用已经够深了。在整个过程中我一直没有写和http连接直接相关的操作,比如ConnectionFactory、ConnectionConfig、HttpRequestExecutor,因为这些都是http方面的操作,对于我们来说是透明的,并不妨碍我们使用,不过看看还是好的,比如我看了HttpRequestExecutor的HttpRequestExecutor.execute(HttpRequest, HttpClientConnection, HttpContext)方法,就看到了http状态码的使用,在100的时候会继续发送socket请求。

博客中应该是有很多的错误,如果有网友看到哪里错了,请联系我,我的qq是1308567317,我好改正过来,一面误导大家。

 

 

 

 

分享到:
评论

相关推荐

    httpcore-4.2.4,httpclient-4.2.5,httpclient-cache-4.2.5,httpmime-4.2.5的jar包下载

    它包含了HTTP连接的建立、保持和关闭,以及HTTP方法(GET, POST等)的实现。HTTP Core还支持传输编码(如chunked编码),内容编码(如gzip压缩)和HTTP连接的复用,这极大地提高了网络通信的效率。 **httpclient-...

    httpclient-4.5.6-API文档-中文版.zip

    赠送jar包:httpclient-4.5.6.jar; 赠送原API文档:httpclient-4.5.6-javadoc.jar; 赠送源代码:httpclient-4.5.6-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.6.pom; 包含翻译后的API文档:httpclient...

    httpclient-4.5.13-API文档-中文版.zip

    赠送jar包:httpclient-4.5.13.jar; 赠送原API文档:httpclient-4.5.13-javadoc.jar; 赠送源代码:httpclient-4.5.13-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.13.pom; 包含翻译后的API文档:...

    httpclient-4.1-alpha1.jar

    httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar

    httpclient-4.5.6-API文档-中英对照版.zip

    赠送jar包:httpclient-4.5.6.jar; 赠送原API文档:httpclient-4.5.6-javadoc.jar; 赠送源代码:httpclient-4.5.6-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.6.pom; 包含翻译后的API文档:httpclient...

    httpclient-4.4.1-API文档-中文版.zip

    赠送jar包:httpclient-4.4.1.jar; 赠送原API文档:httpclient-4.4.1-javadoc.jar; 赠送源代码:httpclient-4.4.1-sources.jar; 赠送Maven依赖信息文件:httpclient-4.4.1.pom; 包含翻译后的API文档:httpclient...

    httpclient-4.5.12-API文档-中英对照版.zip

    赠送jar包:httpclient-4.5.12.jar; 赠送原API文档:httpclient-4.5.12-javadoc.jar; 赠送源代码:httpclient-4.5.12-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.12.pom; 包含翻译后的API文档:...

    httpclient-cache-4.5.jar

    用于http请求的jar包

    httpclient-4.5.5-API文档-中文版.zip

    赠送jar包:httpclient-4.5.5.jar; 赠送原API文档:httpclient-4.5.5-javadoc.jar; 赠送源代码:httpclient-4.5.5-sources.jar; 包含翻译后的API文档:httpclient-4.5.5-javadoc-API文档-中文(简体)版.zip ...

    httpclient-4.5.10-API文档-中文版.zip

    赠送jar包:httpclient-4.5.10.jar; 赠送原API文档:httpclient-4.5.10-javadoc.jar; 赠送源代码:httpclient-4.5.10-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.10.pom; 包含翻译后的API文档:...

    httpclient-4.5.13-API文档-中英对照版.zip

    赠送jar包:httpclient-4.5.13.jar; 赠送原API文档:httpclient-4.5.13-javadoc.jar; 赠送源代码:httpclient-4.5.13-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.13.pom; 包含翻译后的API文档:...

    httpclient-4.5jar

    httpclient-4.5所需jar包,里面包含httpclient-4.5.jar等等10个必须的开发包。 1.commons-codec-1.9.jar 2.commons-logging-1.2.jar 3.fluent-hc-4.5.jar 4.httpclient-4.5.jar 5.httpclient-cache-4.5.jar 6....

    httpclient-4.5.2-API文档-中英对照版.zip

    赠送jar包:httpclient-4.5.2.jar; 赠送原API文档:httpclient-4.5.2-javadoc.jar; 赠送源代码:httpclient-4.5.2-sources.jar; 包含翻译后的API文档:httpclient-4.5.2-javadoc-API文档-中文(简体)-英语-对照...

    httpcomponents-httpclient-4.5.8-bin-src.zip

    Http协议使用封装jar包(commons-codec-1.3.jar、commons-httpclient-3.1.jar、commons-logging-1.1.jar),资源包括: commons-codec-1.11.jar; commons-logging-1.2.jar; fluent-hc-4.5.8.jar; httpclient-...

    httpclient-4.5.13-sources.jar

    httpclient-4.5.13-sources.jar

    httpclient-4.4-API文档-中文版.zip

    赠送jar包:httpclient-4.4.jar; 赠送原API文档:httpclient-4.4-javadoc.jar; 赠送源代码:httpclient-4.4-sources.jar; 赠送Maven依赖信息文件:httpclient-4.4.pom; 包含翻译后的API文档:httpclient-4.4-...

    httpclient-4.3-beta1

    httpclient-4.3-beta1

    commons-httpclient-3.1-rc1.jar ,commons-logging.jar

    在项目中集成HttpClient,通常需要将`commons-httpclient-3.1-rc1.jar`添加到项目的类路径中,然后通过创建HttpClient实例,设置必要的配置,并调用相关方法发起HTTP请求。 接下来是`commons-logging.jar`,它是...

    用httpclient-4.0-alpha2 打造基于http协议的网站分析器

    5. 关闭资源:在完成操作后,记得关闭HttpClient和HttpResponse,以释放系统资源。 ```java response.close(); httpClient.close(); ``` 在这个项目中,"browser"可能是模拟浏览器行为的一个模块,用于发送请求并...

    commons-httpclient-3.0-rc4.rar

    4. **内存泄露**:注意及时关闭HttpMethod和HttpClient,防止内存泄漏。 六、实战应用示例 在实际项目中,HttpClient常用于网页抓取、API调用、文件下载等场景。例如,通过PostMethod发送JSON数据到RESTful API,或...

Global site tag (gtag.js) - Google Analytics