2012-02-29 46 views
19

我正在努力解決以下問題: 我的應用程序使用HttpClient向http服務器發出請求序列。我使用HttpPut將數據發送到服務器。 第一個請求進行得很順利,第二個請求掛起了40秒,然後我發現Connection超時異常。我試圖重用我的HttpClient並通過同一個實例發送第二個請求。如果我用新的ConnectionManager創建新的HttpClient,那麼一切正常。android httpclient掛起對服務器的第二次請求(連接超時)

這是怎麼發生的?以及如何解決它,不要每次創建新的HttpClient?

在此先感謝。

這裏是我的代碼:(如果我評論readClient = newHttpClient(readClient)在doPut,那麼問題就出現了

public class WebTest 
{ 
private HttpClient readClient; 
private SchemeRegistry httpreg; 
private HttpParams params; 

private URI url; //http://my_site.net/data/ 

protected HttpClient newHttpClient(HttpClient oldClient) 
{ 
    if(oldClient != null) 
     oldClient.getConnectionManager().shutdown(); 

    ClientConnectionManager cm = new SingleClientConnManager(params, httpreg); 
    return new DefaultHttpClient(cm, params); 
} 

protected String doPut(String data) 
{ 
    //**************************** 
    //Every time we need to send data, we do new connection 
    //with new ConnectionManager and close old one 
    readClient = newHttpClient(readClient); 

    //***************************** 


    String responseS = null; 
    HttpPut put = new HttpPut(url); 
    try 
    { 
     HttpEntity entity = new StringEntity(data, "UTF-8"); 
     put.setEntity(entity); 
     put.setHeader("Content-Type", "application/json; charset=utf-8"); 
     put.setHeader("Accept", "application/json"); 
     put.setHeader("User-Agent", "Apache-HttpClient/WebTest"); 

     responseS = readClient.execute(put, responseHandler); 
    } 
    catch(IOException exc) 
    { 
     //error handling here 
    } 
    return responseS; 
} 

public WebTest() 
{ 
    httpreg = new SchemeRegistry(); 
    Scheme sch = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); 
    httpreg.register(sch); 

    params = new BasicHttpParams(); 
    ConnPerRoute perRoute = new ConnPerRouteBean(10); 
    ConnManagerParams.setMaxConnectionsPerRoute(params, perRoute); 
    ConnManagerParams.setMaxTotalConnections(params, 50); 
    ConnManagerParams.setTimeout(params, 15000); 
    int timeoutConnection = 15000; 
    HttpConnectionParams.setConnectionTimeout(params, timeoutConnection); 
    // Set the default socket timeout (SO_TIMEOUT) 
    // in milliseconds which is the timeout for waiting for data. 
    int timeoutSocket = 40000; 
    HttpConnectionParams.setSoTimeout(params, timeoutSocket); 
} 

private ResponseHandler<String> responseHandler = new ResponseHandler<String>() 
{ 
    @Override 
    public String handleResponse(HttpResponse response) 
      throws ClientProtocolException, IOException 
    { 
     StatusLine statusLine = response.getStatusLine(); 
     if (statusLine.getStatusCode() >= 300) 
     { 
      throw new HttpResponseException(statusLine.getStatusCode(), 
        statusLine.getReasonPhrase()); 
     } 

     HttpEntity entity = response.getEntity(); 
     if(entity == null) 
      return null; 

     InputStream instream = entity.getContent(); 
     return this.toString(entity, instream, "UTF-8"); 
    } 

    public String toString(
      final HttpEntity entity, 
      final InputStream instream, 
      final String defaultCharset) throws IOException, ParseException 
    { 
     if (entity == null) 
     { 
      throw new IllegalArgumentException("HTTP entity may not be null"); 
     } 

     if (instream == null) 
     { 
      return null; 
     } 
     if (entity.getContentLength() > Integer.MAX_VALUE) 
     { 
      throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); 
     } 
     int i = (int)entity.getContentLength(); 
     if (i < 0) 
     { 
      i = 4096; 
     } 
     String charset = EntityUtils.getContentCharSet(entity); 
     if (charset == null) 
     { 
      charset = defaultCharset; 
     } 
     if (charset == null) 
     { 
      charset = HTTP.DEFAULT_CONTENT_CHARSET; 
     } 

     Reader reader = new InputStreamReader(instream, charset); 

     StringBuilder buffer=new StringBuilder(i); 
     try 
     { 
      char[] tmp = new char[1024]; 
      int l; 
      while((l = reader.read(tmp)) != -1) 
      { 
       buffer.append(tmp, 0, l); 
      } 
     } finally 
     { 
      reader.close(); 
     } 

     return buffer.toString(); 
    } 
}; 

}

+0

服務器可能關閉您的連接。什麼是響應標題? – 2012-02-29 19:33:22

+0

服務器甚至沒有收到我的第二個請求 – 2012-02-29 20:00:00

+2

consumeContent()是答案,謝謝你的詢問 – 2013-06-24 11:46:12

回答

13

聽起來很奇怪,但我有完全相同的問題。我正在製作的應用程序正在做出幾個連續的請求,以下載一組縮略圖以顯示在ListView中,並且在第二個之後它將掛起,就好像在HttpClient代碼中存在死鎖一樣。

The strange修復我發現是使用AndroidHttpClient而不是DefaultHttpClient。一旦我做到了這一點,在走這條路線之前我嘗試了很多東西,它開始工作得很好。請記住在完成請求時調用client.close()。

AndroidHttpClient在文檔中被描述爲DefaultHttpClient和「合理的默認設置和Android註冊方案」。由於這是在api level 8(Android 2.2)中引入的,我挖掘了源代碼以複製這些「默認設置」,以便我可以使用它比api級別更遠。這裏是我的代碼與靜態方法複製的默認值和一個輔助類爲安全地關閉它

public class HttpClientProvider { 

    // Default connection and socket timeout of 60 seconds. Tweak to taste. 
    private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000; 

    public static DefaultHttpClient newInstance(String userAgent) 
    { 
     HttpParams params = new BasicHttpParams(); 

     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 
     HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET); 
     HttpProtocolParams.setUseExpectContinue(params, true); 

     HttpConnectionParams.setStaleCheckingEnabled(params, false); 
     HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT); 
     HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT); 
     HttpConnectionParams.setSocketBufferSize(params, 8192); 

     SchemeRegistry schReg = new SchemeRegistry(); 
     schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 
     schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); 
     ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg); 

     DefaultHttpClient client = new DefaultHttpClient(conMgr, params); 

     return client; 
    } 

} 

而在另一個類...

public static void safeClose(HttpClient client) 
{ 
    if(client != null && client.getConnectionManager() != null) 
    { 
     client.getConnectionManager().shutdown(); 
    } 
} 
+0

非常感謝,我會嘗試這種方法。每次需要發送數據時,您是否在這些更改後對所有請求使用一個實例,或者調用newInstance?你是否在每次請求後調用safeClose? – 2012-02-29 19:59:32

+0

是的...每次都有新的實例,並且每次都安全關閉。我沒有注意到性能受到影響,而我在第一次遇到這個問題時所做的這個應用程序提出了大量的服務請求。 – Rich 2012-02-29 20:01:20

+0

如果我這樣做,每次關閉並打開新實例,則問題消失。但我在文檔中看到,強烈建議重用HttpClient + ConnManager,並且不要爲每個連接創建它,這會節省大量時間和CPU ......我錯了嗎? – 2012-02-29 20:06:24

6

我已經得到了同樣的麻煩時,在循環中執行多個請求。

您可以通過閱讀全部response.getEntity()來解決。

+0

你是如何做到這一點? – CACuzcatlan 2012-09-12 23:27:49

+0

你應該讀流到最後,例如 InputStream is = response.getEntity(); (is.read()!= - 1); – wilddev 2012-09-13 16:42:55

24

聽起來像你沒有消耗完成處理響應後的實體。確保你把下面的代碼finally塊:

if (httpEntity != null) { 
    try { 
     httpEntity.consumeContent(); 
    } catch (IOException e) { 
     Log.e(TAG, "", e); 
    } 
} 

我建議你閱讀HttpClient Tutorial

+0

這解決了我的問題,真的非常感謝你 – 2013-06-24 11:45:03

+2

這應該是正確答案!問題解決了! – GMsoF 2013-07-19 09:04:27

+0

偉大的答案。到一定程度,並用教程備份。 – 2013-09-23 16:41:19

1

我有這個相同的問題。我正在消耗所有內容。

我發現什麼,如果我發出請求後做垃圾回收,一切正常,而無需關閉並創建一個新的AndroidHttpClient:

的System.gc();

3

我想我會詳細說明其他答案。我也遇到過這個問題。問題是因爲我沒有使用內容。

看來,如果你不這樣做,那麼連接將保持不變,並且你無法使用同一個連接發送新的請求。對我來說,這是一個特別難以發現的錯誤,因爲我使用的是android中提供的BasicResponseHandler。代碼看起來像這樣...

public String handleResponse(final HttpResponse response) 
      throws HttpResponseException, IOException { 
     StatusLine statusLine = response.getStatusLine(); 
     if (statusLine.getStatusCode() >= 300) { 
      throw new HttpResponseException(statusLine.getStatusCode(), 
        statusLine.getReasonPhrase()); 
     } 

     HttpEntity entity = response.getEntity(); 
     return entity == null ? null : EntityUtils.toString(entity); 
    } 

因此,如果狀態行超過300,那麼我不會消耗內容。我的情況有內容。我這樣做了我自己的課...

public class StringHandler implements ResponseHandler<String>{ 

    @Override 
    public BufferedInputStream handleResponse(HttpResponse response) throws IOException { 
    public String handleResponse(final HttpResponse response) 
       throws HttpResponseException, IOException { 
      StatusLine statusLine = response.getStatusLine(); 
      HttpEntity entity = response.getEntity(); 
      if (statusLine.getStatusCode() >= 300) { 
       if (entity != null) { 
        entity.consumeContent(); 
       } 
       throw new HttpResponseException(statusLine.getStatusCode(), 
         statusLine.getReasonPhrase()); 
      } 


      return entity == null ? null : EntityUtils.toString(entity); 
     } 
    } 

} 

所以基本上無論如何消耗內容!

+0

其實響應代碼可能不相關;無論如何你都應該嘗試去消費內容。例如。我正在下載圖片,但會得到404回覆,並將「禮貌圖片」作爲內容!當獲得500個響應時,情況也是如此,大多數服務器喜歡爲此提供一個「頁面」,這被視爲內容。 – 2015-12-14 11:53:45

1

這已經夠了問題的解決方案(我有同樣的):

EntityUtils.consume(response.getEntity()); 

空檢查進行內部消耗

+2

有沒有EntityUtils.consume方法?我只能調用entity.consumeContent(); – AFD 2014-08-29 12:12:43

1

由於許多這些答案都是老取決於現在depricated consumeContent()方法,我我以爲我會回答Timeout waiting for connection from pool的問題的替代方案。

HttpEntity someEntity = response.getEntity(); 

    InputStream stream = someEntity.getContent(); 
    BufferedReader rd = new BufferedReader(new InputStreamReader(stream)); 

    StringBuffer result = new StringBuffer(); 
    String line = ""; 
    while ((line = rd.readLine()) != null) { 
     result.append(line); 
    } 
    // On certain android OS levels/certain hardware, this is not enough. 
    stream.close(); // This line is what is recommended in the documentation 

下面是它顯示在文檔中:

cz.msebera.android.httpclient.HttpEntity 
@java.lang.Deprecated 
public abstract void consumeContent() 
          throws java.io.IOException 
This method is deprecated since version 4.1. Please use standard java 
convention to ensure resource deallocation by calling 
InputStream.close() on the input stream returned by getContent() 
相關問題