众所周知,httpclient是java开发中非常常见的一种访问网络资源的方式了。这里不再赘述httpclient强大的功能使用了,比如读取网页(HTTP/HTTPS)内容,以GET或者POST方式向网页提交参数,处理页面重定向,模拟输入用户名和口令进行登录,提交XML格式参数,通过HTTP上传文件,访问启用认证的页面以及httpclient在多线程下的使用.
这里说一下多线程模式下使用httpclient连接池的使用注意事项:
org.apache.http.impl.conn.PoolingClientConnectionManager;
使用这个类就可以使用httpclient连接池的功能了,其可以设置最大连接数和最大路由连接数。
public final static int MAX_TOTAL_CONNECTIONS = 400;
public final static int MAX_ROUTE_CONNECTIONS = 200;
cm = new PoolingClientConnectionManager();
cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
cm.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);
最大连接数就是连接池允许的最大连接数,最大路由连接数就是没有路由站点的最大连接数,比如:
- HttpHostgoogleResearch=newHttpHost("research.google.com",80);
- HttpHostwikipediaEn=newHttpHost("en.wikipedia.org",80);
- cm.setMaxPerRoute(newHttpRoute(googleResearch),30);
- cm.setMaxPerRoute(newHttpRoute(wikipediaEn),50);
并且可以设置httpclient连接等待请求等待时间,相应时间等。
说几个要注意点:
1.首先配置最大连接数和最大路由连接数,如果你要连接的url只有一个,两个必须配置成一样,否则只会取最小值。(这是个坑,默认最大连接是20,每个路由最大连接是2)
2.最好配置httpclient连接等待时间,和相应时间。否则就会一直等待。
httpParams = new BasicHttpParams();
httpParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,CONNECT_TIMEOUT);
httpParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, READ_TIMEOUT);
3 httpclient必须releaseconnection,但不是abort。因为releaseconnection是归还连接到连接池,而abort是直接抛弃这个连接,而且占用连接池的数目。(一定要注意)
HttpGet httpGet = new HttpGet(searchurl);
httpGet.releaseConnection();
4 (一定要注意)httpclient设置的最大连接数绝对不能超过tomcat设置的最大连接数,否则tomcat的连接就会被httpclient连接池一直占用,直到系统挂掉。
5 可以使用tomcat的长连接和htppclient连接池和合理使用来增加系统响应速度。
连接池技术作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响应时间,节省服务器资源开销。其优势主要有两个:其一是减少创建连接的资源开销,其二是资源的访问控制。连接池管理的对象是长连接,对于HTTP连接是否适用,我们需要首先回顾一下长连接和短连接。
所谓长连接是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接,而短连接则每次数据传输都需要客户端和服务器端建立一次连接。长连接的优势在于省去了每次数据传输连接建立的时间开销,能够大幅度提高数据传输的速度,对于P2P应用十分适合,但是对于诸如Web网站之类的B2C应用,并发请求量大,每一个用户又不需频繁的操作的场景下,维护大量的长连接对服务器无疑是一个巨大的考验。而此时,短连接可能更加适用。但是短连接每次数据传输都需要建立连接,我们知道HTTP协议的传输层协议是TCP协议,TCP连接的建立和释放分别需要进行3次握手和4次握手,频繁的建立连接即增加了时间开销,同时频繁的创建和销毁Socket同样是对服务器端资源的浪费。所以对于需要频繁发送HTTP请求的应用,需要在客户端使用HTTP长连接。
HTTP连接是无状态的,这样很容易给我们造成HTTP连接是短连接的错觉,实际上HTTP1.1默认即是持久连接,HTTP1.0也可以通过在请求头中设置Connection:keep-alive使得连接为长连接。既然HTTP协议支持长连接,我们就有理由相信HTTP连接同样需要连接池技术来管理和维护连接建立和销毁。HTTP Client4.0的ThreadSafeClientConnManager实现了HTTP连接的池化管理,其管理连接的基本单位是Route(路由),每个路由上都会维护一定数量的HTTP连接。这里的Route的概念可以理解为客户端机器到目标机器的一条线路,例如使用HttpClient的实现来分别请求 www.163.com 的资源和 www.sina.com 的资源就会产生两个route。缺省条件下对于每个Route,HttpClient仅维护2个连接,总数不超过20个连接,显然对于大多数应用来讲,都是不够用的,可以通过设置HTTP参数进行调整。
HttpParams params = new BasicHttpParams();
//将每个路由的最大连接数增加到200
ConnManagerParams.setMaxTotalConnections(params,200);
// 将每个路由的默认连接数设置为20
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);
// 设置某一个IP的最大连接数
HttpHost localhost = new HttpHost("locahost", 80);
connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register( new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register( new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);
可以配置的HTTP参数有:
1) http.conn-manager.timeout 当某一线程向连接池请求分配线程时,如果连接池已经没有可以分配的连接时,该线程将会被阻塞,直至http.conn-manager.timeout超时,抛出ConnectionPoolTimeoutException。
2) http.conn-manager.max-per-route 每个路由的最大连接数;
3) http.conn-manager.max-total 总的连接数;
连接的有效性检测是所有连接池都面临的一个通用问题,大部分HTTP服务器为了控制资源开销,并不会
永久的维护一个长连接,而是一段时间就会关闭该连接。放回连接池的连接,如果在服务器端已经关闭,客
户端是无法检测到这个状态变化而及时的关闭Socket的。这就造成了线程从连接池中获取的连接不一定是有效的。这个问题的一个解决方法就是在每次请求之前检查该连接是否已经存在了过长时间,可能已过期。但是这个方法会使得每次请求都增加额外的开销。HTTP Client4.0的ThreadSafeClientConnManager 提供了
closeExpiredConnections()方法和closeIdleConnections()方法来解决该问题。前一个方法是清除连接池中所有过期的连接,至于连接什么时候过期可以设置,设置方法将在下面提到,而后一个方法则是关闭一定时间空闲的连接,可以使用一个单独的线程完成这个工作。
public static class IdleConnectionMonitorThread extends Thread{
private final ClientConnectionManagerconnMgr;
privatevolatilebooleanshutdown;
public IdleConnectionMonitorThread(ClientConnectionManagerconnMgr){
super();
this.connMgr=connMgr;
}
@Override
public void run(){
try{
while(!shutdown){
synchronized(this){
wait(5000);//关闭过期的连接connMgr.closeExpiredConnections();//关闭空闲时间超过30秒的连接connMgr.closeIdleConnections(30,
TimeUnit.SECONDS);
}
}
}catch(InterruptedExceptionex){
//terminate
}
}
public void shutdown(){
shutdown=true;
synchronized(this){
notifyAll();
}
刚才提到,客户端可以设置连接的过期时间,可以通过HttpClient的setKeepAliveStrategy方法设置连接的过期时间,这样就可以配合closeExpiredConnections()方法解决连接池中连接失效的。
DefaultHttpClient httpclient=newDefaultHttpClient();
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy(){
public long getKeepAliveDuration(HttpResponseresponse,HttpContextcontext){
//Honor'keep-alive'headerHeaderElementIteratorit=newBasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));while(it.hasNext()){
HeaderElement he=it.nextElement();
String param= he.getName();
String value=he.getValue();
if(value!=null&¶m.equalsIgnoreCase("timeout")){
try{
return Long.parseLong(value)*1000;
}catch(NumberFormatExceptionignore){
}
}
}
HttpHost target=(HttpHost)context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
if("www.163.com".equalsIgnoreCase(target.getHostName())){
//对于163这个路由的连接,保持5秒return5*1000;
}else{
//其他路由保持30秒return30*1000;
}
}
})