【问题标题】:BindException: address already in use on a client socket?BindException:地址已在客户端套接字上使用?
【发布时间】:2009-12-30 02:11:41
【问题描述】:

我有一个客户端-服务器分层架构,客户端向服务器发出类似 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)

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

【问题讨论】:

    标签: java sockets httpclient


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      每秒有数百个连接,并且不知道您的连接保持打开、执行其操作、关闭和回收多长时间,我怀疑这只是您将遇到的问题。您可以做的一件事是在您的 try 块中捕获 BindException,在绑定失败的情况下使用它来执行您需要做的任何事情,并将整个调用包装在一个 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();
      

      【讨论】:

      • 每台主机:2,最大:20。我怎么能在如此保守的默认值下超过任何套接字限制......:-p
      【解决方案3】:

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

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

      【讨论】:

      • 它不会因为一个更倾向于耗尽所述资源的异常而失败吗?大多数资源耗尽错误来自 socket() 调用——手册页提到了至少三种由于资源不足而导致失败的不同方式,而绑定手册页没有。我希望 Java 异常能够表明这一点。您仍然认为这可能是个问题吗?
      • 如果 HttpClient 被编码为这样做,它只会因这样的异常而失败——我没有查看 HttpClient#executeMethod 的代码,但根据您的经验,他们似乎没有。可以合理地认为他们可能在内部捕获了 BindException 并在不同的客户端端口上重试了连接,但话又说回来,这会打开一整罐蠕虫——他们重试了多少次?他们怎么知道这是你想要做的? -- 他们只是抛出 BindException 并让你决定也是有道理的。
      • 通过查看跟踪,您可以看到它是直接从本机 Socket 连接方法抛出的
      • 当然,史蒂文,但我的观点是HttpClient 可以捕捉到抛出的异常并对其进行处理——根据我所说的警告,仍然很难决定重试多少次它应该尝试等等,并且期望HttpClient 允许异常传递也是合理的,这样您就可以决定如何自己处理它。
      • 当然可以让我处理;我只是表示“您发出的请求多于允许打开的 TCP/IP 端口”的评论与我得到的错误不匹配。
      【解决方案4】:

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

      感谢所有建议!

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-01
          • 2014-06-13
          • 1970-01-01
          相关资源
          最近更新 更多