天天看點

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參數進行調整。

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&&param.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;
    }
  }
})           

繼續閱讀