【问题标题】:Why OkHttp doesn't reuse its connections?为什么 OkHttp 不重用它的连接?
【发布时间】:2017-04-22 00:46:12
【问题描述】:

我正在使用 OkHttp 3.5.0 执行 http 基准测试。我正在向同一个 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.body().string()而不是response.close(),那么SocketException不会发生,但netstat仍然显示大量TIME_WAIT连接,并且基准性能越来越低。

我做错了什么?

PS:我尝试过使用 Apache HttpClient 及其PoolingHttpClientConnectionManager,看起来效果很好。但我想弄清楚 OkHttp 有什么问题。

【问题讨论】:

  • 你可以试试 response.body().string() 然后是 response.close()。您应该使用响应以便可以重用连接(假设 HTTP/1.1),然后您应该关闭响应。
  • threadCount 是什么?
  • @YuriSchimke 没有任何改变。其实response.body().string()消费后执行close()。
  • @JesseWilson 从示例代码中删除了threadCount。它只是一些核心。
  • @JesseWilson 好像我也有同样的问题。在基准测试期间性能会急剧下降。我不知道 JSnow 是否给你发了测试用例,但无论如何,这是我的:github.com/olegcherr/OkHttp-TimeWait-Test 另外,为了更方便,我创建了一个讨论:github.com/olegcherr/OkHttp-TimeWait-Test/issues/1

标签: http benchmarking okhttp


【解决方案1】:

我的版本是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();
}

在 StreamAllocation.java 第 367 行:

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);
    }
}

这意味着如果请求或响应中存在“Connection: close”标头,okhttp 将CLOSE 连接

虽然问题已经很久没有提交了,但我希望这个答案能帮助遇到这个问题的人,祝你好运。

【讨论】:

    【解决方案2】:

    感谢 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();
    

    【讨论】:

      猜你喜欢
      • 2019-03-11
      • 1970-01-01
      • 1970-01-01
      • 2022-11-09
      • 2018-05-10
      • 2020-05-27
      • 2021-10-11
      • 2020-11-12
      相关资源
      最近更新 更多