为啥 OkHttp 不重用它的连接?

Posted

技术标签:

【中文标题】为啥 OkHttp 不重用它的连接?【英文标题】: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 【参考方案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();

【讨论】:

以上是关于为啥 OkHttp 不重用它的连接?的主要内容,如果未能解决你的问题,请参考以下文章

OkHttp3-连接(Connections)

与 OkHttp 连接的 Android http 不工作

为啥这种方式的 TCP 打孔不起作用?

三、深入理解OkHttp:连接处理-ConnectIntercepter

OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?

为啥总是关闭数据库连接?