【问题标题】:android httpclient hangs on second request to the server (connection timed out)android httpclient 挂起对服务器的第二个请求(连接超时)
【发布时间】:2012-02-29 19:26:51
【问题描述】:

我正在努力解决以下问题: 我的应用程序使用 HttpClient 向 http 服务器发出一系列请求。我使用 HttpPut 向服务器发送数据。 第一个请求进展顺利,第二个请求挂起 40 秒,然后我捕获 Connection timed out 异常。我正在尝试重用我的 HttpClient 并通过同一个实例发送第二个请求。如果我创建新的 HttpClient 和新的 ConnectionManager,那么一切正常。

为什么会这样?以及如何解决它而不是每次都创建新的HttpClient?

提前致谢。

这是我的代码:(如果我在 doPut 中注释 readClient = newHttpClient(readClient),那么问题就出现了。

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();
    }
}; 

}

【问题讨论】:

  • 服务器可能正在关闭您的连接。什么是响应头?
  • 服务器甚至没有收到我的第二个请求
  • consumeContent() 是答案,感谢您的提问
  • 如何使用 okHttp 库实现这一点,我正在进行同步网络调用。这里是同步调用代码 response = okHttpClient.newCall(request).execute();

标签: android httpclient


【解决方案1】:

听起来您在处理完响应后没有使用实体。确保将以下代码放在 finally 块中:

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

我建议你阅读HttpClient Tutorial

【讨论】:

  • 我使用的是 Xamarin Android 并且遇到了同样的问题 - 处理 http 响应消息对我来说很有效 - httpResponseMessage.Dispose();感谢您的提示:)
【解决方案2】:

听起来很奇怪,但我遇到了完全相同的问题。我正在开发的应用程序正在发出几个连续的请求,以下载一堆缩略图图像以显示在 ListView 中,在第二个请求之后它会挂起,就好像 HttpClient 代码中存在死锁一样。

我发现的奇怪解决方法是使用 AndroidHttpClient 而不是 DefaultHttpClient。一旦我这样做了,并且在走这条路线之前我尝试了很多东西,它就开始工作得很好。请记住在完成请求后调用 client.close()。

AndroidHttpClient 在文档中被描述为具有“合理的默认设置和 Android 注册方案”的 DefaultHttpClient。由于这是在 api 级别 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();
    }
}

【讨论】:

  • 非常感谢,我试试这个方法。在这些更改之后,您是对所有请求使用一个实例,还是在每次需要发送数据时调用 newInstance?每次请求后你都会调用 safeClose 吗?
  • 如果每次请求都重新创建 httpClient 并在之后关闭它,为什么要使用 ThreadSafeClientConnManager 而不是 SingleClientConnManager?
  • 连续尝试 3 次是否有效?此解决方案(每次不创建新对象)适用于 2 个请求,但不适用于 3 个。
  • 这不是问题的解决方案,这是一种解决方法。您可以而且应该重用 http 客户端对象。下面由 neevek 发布的答案对我有用(通过调用 entity.consumeContent() 来使用 HttpResponse 实体)。
  • 我正在阅读一个实时的、无限的流。 consumeContent 无限期阻塞,它似乎坚持尝试重用连接。为关闭 connectionManager 欢呼三声。为我工作!
【解决方案3】:

在循环中执行多个请求时,我遇到了同样的问题。

你可以通过阅读response.getEntity()全部来解决它。

【讨论】:

  • 你应该把流读到最后,例如 InputStream is = response.getEntity(); while(is.read()!=-1);
【解决方案4】:

我想我会详细说明其他答案。我也遇到过这个问题。问题是因为我没有消费内容。

如果您不这样做,那么连接将保持不变,并且您无法使用同一连接发送新请求。对我来说,这是一个特别难以发现的错误,因为我使用的是 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);
        }
    }

}

所以基本上无论如何都要消耗内容!

【讨论】:

  • 其实响应码可能不相关;无论如何,您都应该尝试消费内容。例如。我正在下载图片,但会收到 404 响应和“礼貌图片”作为内容!收到 500 条响应时也是如此,大多数服务器都喜欢为此提供一个“页面”,这也算是内容。
【解决方案5】:

我也遇到过同样的问题。我正在消费所有内容。

我发现如果我在发出请求后进行垃圾回收,一切正常,无需关闭并创建新的 AndroidHttpClient:

System.gc();

【讨论】:

    【解决方案6】:

    解决问题就足够了(我也有):

    EntityUtils.consume(response.getEntity());
    

    在消费内部执行空值检查

    【讨论】:

    • 没有EntityUtils.consume方法?我只能调用 entity.consumeContent();
    【解决方案7】:

    由于这些答案中的许多都是旧的并且依赖于现在已被弃用的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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-31
      • 1970-01-01
      • 2019-10-14
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      相关资源
      最近更新 更多