3

我有一个客户端-服务器分层架构,客户端向服务器发出类似 RPC 的请求。我正在使用 Tomcat 来托管 servlet,并使用 Apache HttpClient 来向它发出请求。

我的代码是这样的:

    private static final HttpConnectionManager CONN_MGR = new MultiThreadedHttpConnectionManager();
    final GetMethod get = new GetMethod();
    final HttpClient httpClient = new HttpClient(CONN_MGR);
    get.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
    get.getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);

    get.setQueryString(encodedParams);
    int responseCode;
    try {
        responseCode = httpClient.executeMethod(get);
    } catch (final IOException e) {
        ...
    }
    if (responseCode != 200)
        throw new Exception(...);

    String responseHTML;
    try {
        responseHTML = get.getResponseBodyAsString(100*1024*1024);
    } catch (final IOException e) {
        ...
    }
    return responseHTML;

它在轻负载环境中运行良好,但是当我每秒发出数百个请求时,我开始看到这一点 -

Caused by: java.net.BindException: Address already in use
    at java.net.PlainSocketImpl.socketBind(Native Method)
    at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:336)
    at java.net.Socket.bind(Socket.java:588)
    at java.net.Socket.<init>(Socket.java:387)
    at java.net.Socket.<init>(Socket.java:263)
    at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:80)
    at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:122)
    at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707)
    at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:387)
    at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)

关于如何解决这个问题的任何想法?我猜这与尝试重用临时客户端端口的客户端有关,但为什么会发生这种情况/我该如何解决?谢谢!

4

5 回答 5

2

可以在这里找到对您遇到的问题的很好的讨论。在 Tomcat 端,默认情况下它将使用SO_REUSEADDR选项,这将允许服务器重用处于 TIME_WAIT 中的套接字。此外,Apache http 客户端默认使用 keep-alives,并尝试重用连接。

您的问题似乎是由于未在 HttpClient 上调用releaseConnection引起的。这是重用连接所必需的。否则,连接将保持打开状态,直到垃圾收集器来关闭它,或者服务器断开保持连接。在这两种情况下,它都不会返回到池中。

于 2009-12-30T03:37:17.890 回答
1

每秒有数百个连接,并且不知道您的连接保持打开、执行其操作、关闭和回收多长时间,我怀疑这只是您将遇到的问题。您可以做的一件事是BindException在您的 try 块中捕获 ,在绑定不成功的情况下使用它来执行您需要执行的任何操作,并将整个调用包装在一个while循环中,该循环取决于指示绑定是否成功的标志。在我的头顶上:

boolean hasBound = false;
while (!hasBound) {
    try {
        hasBound = true;
        responseCode = httpClient.executeMethod(get);
    } catch (BindException e) {
        // do anything you want in the bound-unsuccessful case
    } catch (final IOException e) {
        ...
    }
}

更新问题:一个奇怪的问题:您允许的最大总连接数和每主机连接数是MultiThreadedHttpConnectionManager多少?在您的代码中,这将是:

CONN_MGR.getParams().getDefaultMaxConnectionsPerHost();
CONN_MGR.getParams().getMaxTotalConnections();
于 2009-12-30T02:23:01.660 回答
0

所以事实证明问题是其他 HttpClient 实例之一意外地没有使用我实例化的 MultiThreadedHttpConnectionManager,所以我实际上根本没有速率限制。修复这个问题修复了抛出的异常。

不过,感谢所有建议!

于 2009-12-31T01:29:31.790 回答
0

因此,您发出的请求多于允许打开的 TCP/IP 端口。我不做HttpClient,所以我不能详细说明这个问题,但理论上对于这个特定问题有三种解决方案:

  1. 基于硬件:添加另一个 NIC(网络接口卡)。
  2. 基于软件:使用后直接关闭连接和/或增加连接超时。
  3. 基于平台:增加允许打开的 TCP/IP 端口数量。可能是特定于操作系统和/或特定于 NIC 驱动程序的。绝对最大值为 65535,其中一些可能已被保留/正在使用(例如端口 80)。
于 2009-12-30T02:24:08.193 回答
0

即使我们调用 HttpClientUtils.closeQuietly(client); 但是在您的代码中,如果尝试从 HttpResponse 实体(如 InputStream contentStream = HttpResponse.getEntity().getContent())读取内容,那么您也应该关闭输入流,然后只有 HttpClient 连接才能正确关闭。

于 2017-06-29T16:24:38.493 回答