Почему OkHttp не использует повторно свои соединения?

Я выполняю http-тест, используя OkHttp 3.5.0. Я отправляю тысячи запросов на один и тот же URL.

Я ожидаю, что OkHttp-клиент использует ConnectionPool и повторно использует свои соединения снова и снова. Но если мы посмотрим на netstat, мы увидим много соединений в состоянии TIME_WAIT:

TCP    127.0.0.1:80           127.0.0.1:51752        TIME_WAIT
TCP    127.0.0.1:80           127.0.0.1:51753        TIME_WAIT
TCP    127.0.0.1:80           127.0.0.1:51754        TIME_WAIT
TCP    127.0.0.1:80           127.0.0.1:51755        TIME_WAIT
TCP    127.0.0.1:80           127.0.0.1:51756        TIME_WAIT
...

После пары тысяч запросов я получаю SocketException: No buffer space available (maximum connections reached?)

Код, формирующий запросы (Kotlin):

val client = OkHttpClient.Builder()
        .connectionPool(ConnectionPool(5, 1, TimeUnit.MINUTES))
        .build()

val request = Request.Builder().url("http://192.168.0.50").build()

while (true) {
    val response = client.newCall(request).execute()
    response.close()
}

Если вместо response.close() я использую response.body().string(), то SocketException не происходит, но netstat по-прежнему показывает много соединений TIME_WAIT, и производительность бенчмарка становится все ниже и ниже.

Что я делаю неправильно?

PS: я пытался использовать Apache HttpClient и его PoolingHttpClientConnectionManager, и, похоже, он отлично работает. Но я хотел бы выяснить, что не так с OkHttp.


person J. Snow    schedule 07.12.2016    source источник
comment
Можете ли вы попробовать response.body().string(), а затем response.close(). Вы должны использовать ответ, чтобы соединение можно было использовать повторно (при условии, что HTTP/1.1), а затем вы должны закрыть ответ.   -  person Yuri Schimke    schedule 07.12.2016
comment
Что такое threadCount ?   -  person Jesse Wilson    schedule 07.12.2016
comment
@YuriShimke Ничего не изменилось. На самом деле response.body().string() выполняет close() после потребления.   -  person J. Snow    schedule 07.12.2016
comment
@JesseWilson Удален threadCount из примера кода. Это было просто несколько ядер.   -  person J. Snow    schedule 07.12.2016
comment
Хм. Я не видел этого в другом месте. Можете ли вы изолировать его в тестовом примере?   -  person Jesse Wilson    schedule 08.12.2016
comment
@JesseWilson Кажется, у меня тот же вопрос. Производительность резко снижается во время теста. Я не знаю, отправил ли вам JSnow тестовый пример, но в любом случае, вот мой: github. com/olegcherr/OkHttp-TimeWait-Test Также я создал обсуждение для большего удобства: github.com/olegcherr/OkHttp-TimeWait-Test/issues/1   -  person Oleg Cherr    schedule 10.12.2016
comment
OkHttp теперь имеет версию 3.11.0, и он по-прежнему не использует повторно соединения. @ J.Snow, ты нашел способ настроить его для повторного использования соединений?   -  person Anton Cherkashyn    schedule 17.10.2018
comment
@AntonCherkashyn Нет(( Ничего. Как вы столкнулись с этой проблемой? Каков ваш вариант использования?   -  person J. Snow    schedule 17.10.2018


Ответы (2)


моя версия 3.13.0, что недалеко от 3.5.0, и я тоже столкнулся с проблемой TIME_WAIT.

после погружения в исходный код я нашел в CallServerInterceptor.java строке 142:

if ("close".equalsIgnoreCase(response.request().header("Connection"))
    || "close".equalsIgnoreCase(response.header("Connection"))) {
   streamAllocation.noNewStreams();
}

и в строке 367 StreamAllocation.java:

public void noNewStreams() {
    Socket socket;
    Connection releasedConnection;
    synchronized (connectionPool) {
      releasedConnection = connection;
      socket = deallocate(true, false, false); // close connection!
      if (connection != null) releasedConnection = null;
    }
    closeQuietly(socket);
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
}

что означает, что okhttp будет CLOSE соединение, если заголовок «Connection: close» существует либо в запросе, либо в ответе.

хотя с момента отправки вопроса прошло много времени, я надеюсь, что этот ответ поможет ребятам, столкнувшимся с этой проблемой, удачи.

person toien    schedule 04.07.2019

Спасибо toien, и я нашел свое решение (для нестандартного http-сервера).

public class Http1CodecWrapper implements HttpCodec {
    private HttpCodec codec;

    public Http1CodecWrapper(HttpCodec codec) {
        this.codec = codec;
    }

    // ...

    @Override
    public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
        return codec.readResponseHeaders(expectContinue)
            .addHeader("Connection", "keep-alive");
    }
}

OkHttpClient httpClient = new OkHttpClient.Builder()
    .addNetworkInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                RealInterceptorChain realChain = (RealInterceptorChain) chain;
                Request request = realChain.request();
                StreamAllocation allocation = realChain.streamAllocation();
                HttpCodec codec = new Http1CodecWrapper(realChain.httpStream());
                RealConnection connection = (RealConnection) realChain.connection();

                return realChain.proceed(request, allocation, codec, connection);
            }
        })
    .build();
person Wang Qian    schedule 05.06.2020