Apache DefaultHttpClient - java.net.BindException: Address already in use: connect Apache DefaultHttpClient - java.net.BindException: 地址已在使用中: 连接

3

我正在运行一个性能测试,使用Java “客户端” 访问Tomcat 8.5 Web服务器。大约在第13000个请求后,HTTP请求会失败,并出现以下错误:

org.apache.http.impl.client.DefaultHttpClient tryConnect
INFO: Retrying connect
java.net.BindException: Address already in use: connect
    at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
    at java.net.PlainSocketImpl.connect(Unknown Source)
    at java.net.SocksSocketImpl.connect(Unknown Source)
    at java.net.Socket.connect(Unknown Source)
    at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:127)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
    at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294)
    at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:643)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)

代码如下:
    for (int i = 0; i < 15000; i++) {
        try {
            if (i % 1000 == 0) System.out.println("Iterations: " + Integer.toString(i));
            HttpGet request = new HttpGet("http://localhost:9080");
            DefaultHttpClient client = new DefaultHttpClient();
            HttpResponse response = client.execute(request, new BasicHttpContext());
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Iterations: " + Integer.toString(i));
            System.exit(1);
        }
    }

如果我缓存DefaultHttpClient,则不会出现错误。也尝试过,
        request.releaseConnection();
        client.getConnectionManager().shutdown();

但是错误没有改变。 看起来错误不是由客户端引起的。如果我在URL中访问另一个网站,那么就可以了。似乎是由于运行在Windows上的Tomcat耗尽了文件句柄或套接字资源等造成的。 如果我在崩溃之后再次运行它作为另一个进程,它将在1次运行而不是13,000次失败,因此问题是Tomcat耗尽了资源。似乎DefaultHttpClient没有关闭它的连接,或者Tomcat在进行垃圾回收之前不释放它的连接。

使用HTTPClient 4.2.5

有任何想法为什么会发生这种情况,或者如何解决?


1
错误是由客户端引起的。您的本地端口用尽了,因此可能存在连接泄漏。可能不在这段代码中。 - user207421
很明显你需要缓存客户端。这样可以进行连接池,但如果你不断实例化新的对象,就无法实现连接池。 - user207421
如果问题是客户端用完了端口,那么为什么如果我使用的不是本地Tomcat的URL就不会发生这种情况呢?看起来与服务器有关。这是测试的唯一代码,连接泄漏在哪里? - James
DefaultHttpClient在执行请求后不会释放其连接吗?添加releaseConnection()和getConnectionManager().shutdown()也无法改变这种行为。 - James
1
你尝试过使用 client.close() 或者将客户端放在 try-with-resources 语句块中吗?在我的代码中,我使用了 CloseableHttpClientCloseableHttpResponse 接口,并且都是通过 try-with-resources 语句块关闭的。 - CoronA
使用4.2.5版本,没有CloseableHttpClient或close()函数。 - James
3个回答

5

我无法重现你遇到的同样错误。不管怎样,当我在单线程中运行你的示例时,我收到了NoRouteToHostException。

13:37:57.917 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Releasing connection org.apache.http.impl.conn.ManagedClientConnectionImpl@2c7ceffa
Iterations: 16329
java.net.NoRouteToHostException: Can't assign requested address (Address not available)
        at java.base/java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399)
        at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:242)
        at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:224)
        at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:403)
        at java.base/java.net.Socket.connect(Socket.java:591)
        at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121)
        at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
        at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:326)
        at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:605)
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:440)
        at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at com.demo.DemoApplication.main(DemoApplication.java:25)

你正在以比关闭连接更快的速率打开连接 - 关闭套接字在释放给新连接之前会保持TIME_WAIT状态。
仅供测试目的,您可以设置tcp_tw_reuse,以允许复用TIME_WAIT套接字。
在我的OSX上,我可以更改最大段寿命进行测试,然后错误消失了。
sudo sysctl -w net.inet.tcp.msl=1000 net.inet.tcp.msl:15000-> 1000 DefaultHttpClientBasicClientConnectionManager支持,它创建和管理单个连接,并为任何路线同时保持一个活动连接。
当连接释放回连接管理器时,它被保持为可重用并标记为可重用。

如果我缓存DefaultHttpClient,则不会出现错误。

这正是解决方案。我认为正确的方法是使用单个http客户端并允许连接管理器执行其工作。
所有的连接管理都在apache文档中有详细解释。
4.2.5版本相对较旧(2013年4月发布)。如果您正在开始一个新项目,更新到最新版本(4.5.12)是有意义的。
参考资料:

https://cwiki.apache.org/confluence/display/HTTPCOMPONENTS/FrequentlyAskedConnectionManagementQuestions

https://raby.sh/osx-where-are-my-time_wait.html


2

看起来你正在错误地使用HttpClient(即可能复制粘贴旧的遗留代码片段)

  • 首先,一个客户端足矣(对于一个线程)。
  • 根据httpcomponents-client文档,最好使用池化连接管理。
  • 应该关闭你的响应。

也就是说:

  PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
  CloseableHttpClient client = HttpClients.custom()
          .setConnectionManager(cm)
          .build();
  HttpContext context = HttpClientContext.create();
  try {
    final HttpGet request = new HttpGet("http://localhost:9080");
    for(int i = 0; i < 15000; i++) {
      CloseableHttpResponse response = httpClient.execute(request, context);
      try {
        HttpEntity entity = response.getEntity();
        EntityUtils.consume(entity);
      } finally {
        response.close();
      }
    }
  } finally {
    client.close();
  }
}

我正在使用4.2.5版本,你的代码基于更新版本中不存在的新类。 - James

1
最佳实践是使用多线程HttpClient。以下代码应该可以工作:
package com.demo.httpclient;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.DefaultClientConnection;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.util.EntityUtils;

public class ThreadedHttpClientTest {

    private static URI rootUri = URI.create("http://localhost:8080/");
    private static int worker = 100;
    private static int count = 15000;

    private static HttpClient httpClient;

    public static void main(String[] args) throws Exception {

        long startTime = System.currentTimeMillis();
        PoolingClientConnectionManager poolingClientConnectionManager = new PoolingClientConnectionManager();
        poolingClientConnectionManager.setMaxTotal(worker);
        poolingClientConnectionManager.setDefaultMaxPerRoute(worker);

        httpClient = new DefaultHttpClient(poolingClientConnectionManager);

        List<Callable<Void>> workers = new ArrayList<Callable<Void>>();

        for (int i = 0; i < count; i++) {
            workers.add(new WorkerThread(httpClient, rootUri.toString()));
        }

        ExecutorService pool = Executors.newFixedThreadPool(worker);

        int i=0;
        for (Future<Void> future : pool.invokeAll(workers)) {
            future.get();
            System.out.println("Response " + i++);
        }

        System.out.println("Time Taken :: " + (System.currentTimeMillis() - startTime) + "ms");
        pool.shutdown();
    }

    static class WorkerThread implements Callable<Void> {

        HttpClient client;
        String url;

        public WorkerThread(HttpClient httpClient, String url) {
            this.client = httpClient;
            this.url = url;
        }

        @Override
        public Void call() throws Exception {
            HttpGet get = new HttpGet(url);
            HttpResponse response = client.execute(get, new DefaultClientConnection());
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
            return null;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接