Apache DefaultHttpClient - java. net .BindException: адрес уже используется: подключиться - PullRequest
3 голосов
/ 07 мая 2020

Я запускаю тест производительности на «клиенте» Java, обращающемся к веб-серверу Tomcat 8.5. Примерно после 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-адресу, это нормально. Кажется, это вызвано Tomcat в Windows, у которого заканчиваются дескрипторы файлов или ресурсы сокета или что-то в этом роде. Если я снова запущу его как другой процесс сразу после сбоя, он завершится ошибкой за 1 запуск, а не за 13000, поэтому проблема в том, что Tomcat исчерпывает ресурсы. Похоже, что DefaultHttpClient не закрывает свое соединение, или Tomcat не освобождает свои соединения до тех пор, пока не произойдет ag c.

Использование HTTPClient 4.2.5

Любые идеи, почему это происходит, или как исправить ?

Ответы [ 3 ]

5 голосов
/ 16 мая 2020

Я не смог воспроизвести ту же ошибку, что и у вас. В любом случае, когда я запускал ваш пример в одном потоке, я получаю 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

DefaultHttpClient поддерживается BasicClientConnectionManager, которые создают и управляют одним соединением и поддерживают только одно активное соединение за раз для любого маршрута.

Когда соединение возвращается обратно в диспетчер соединений, оно остается активным для повторного использования и помечается как пригодное для повторного использования.

Если я кэширую DefaultHttpClient, ошибка не возникает.

Это именно решение. Я считаю правильным использовать один http-клиент и позволить менеджерам соединений выполнять свою работу.

Все управление подключениями объясняется в apache документации

4.2 .5 довольно старый (апрель 2013 г.). Если вы начинаете новый проект, имеет смысл обновить его до последней версии (4.5.12) на момент написания.

Ссылки:

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

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

2 голосов
/ 12 мая 2020

Похоже, вы используете 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();
  }
}
0 голосов
/ 13 мая 2020

Лучше всего использовать MultiThread 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;
        }
    }
}
...