1. 程式人生 > >HttpClient連線池使用

HttpClient連線池使用

眾所周知,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); 

最大連線數就是連線池允許的最大連線數,最大路由連線數就是沒有路由站點的最大連線數,比如:

  1. HttpHostgoogleResearch=newHttpHost("research.google.com",80);
  2. HttpHostwikipediaEn=newHttpHost("en.wikipedia.org",80);
  3. cm.setMaxPerRoute(newHttpRoute(googleResearch),30);
  4. 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引數進行調整。

HttpParamsparams=newBasicHttpParams();

//將每個路由的最大連線數增加到200

ConnManagerParams.setMaxTotalConnections(params,200);

// 將每個路由的預設連線數設定為20

ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);

// 設定某一個IP的最大連線數

HttpHost localhost = new HttpHost("locahost", 80); connPerRoute.setMaxForRoute(newHttpRoute(localhost),50); ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute); SchemeRegistry schemeRegistry =newSchemeRegistry(); schemeRegistry.register( newScheme("http",PlainSocketFactory.getSocketFactory(),80)); schemeRegistry.register( newScheme("https",SSLSocketFactory.getSocketFactory(),443)); ClientConnectionManager cm =newThreadSafeClientConnManager(params, schemeRegistry); HttpClient httpClient =newDefaultHttpClient(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()方法來解決該問題。前一個方法是清除連線池中所有過期的連線,至於連線什麼時候過期可以設定,設定方法將在下面提到,而後一個方法則是關閉一定時間空閒的連線,可以使用一個單獨的執行緒完成這個工作。

publicstaticclassIdleConnectionMonitorThreadextendsThread{ privatefinalClientConnectionManager connMgr; privatevolatileboolean shutdown; publicIdleConnectionMonitorThread(ClientConnectionManager connMgr){ super(); this.connMgr = connMgr; } @Override publicvoid run(){ try{ while(!shutdown){ synchronized(this){ wait(5000); // 關閉過期的連線 connMgr.closeExpiredConnections(); // 關閉空閒時間超過30秒的連線 connMgr.closeIdleConnections(30,TimeUnit.SECONDS); } } }catch(InterruptedException ex){ // terminate } } publicvoid shutdown(){ shutdown =true; synchronized(this){ notifyAll(); }

      剛才提到,客戶端可以設定連線的過期時間,可以通過HttpClient的setKeepAliveStrategy方法設定連線的過期時間,這樣就可以配合closeExpiredConnections()方法解決連線池中連線失效的。

DefaultHttpClient httpclient =newDefaultHttpClient(); httpclient.setKeepAliveStrategy(newConnectionKeepAliveStrategy(){ publiclong getKeepAliveDuration(HttpResponse response,HttpContext context){ // Honor 'keep-alive' header HeaderElementIterator it =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&& param.equalsIgnoreCase("timeout")){ try{ returnLong.parseLong(value)*1000; }catch(NumberFormatException ignore){ } } } HttpHost target =(HttpHost) context.getAttribute( ExecutionContext.HTTP_TARGET_HOST); if("www.163.com".equalsIgnoreCase(target.getHostName())){ // 對於163這個路由的連線,保持5秒 return5*1000; }else{ // 其他路由保持30秒 return30*1000; } } })