为什么OkHttp没有重用它的连接?

时间:2016-12-07 07:02:54

标签: http benchmarking okhttp

我正在使用OkHttp 3.5.0执行http基准测试。我将数千个请求发送到同一个网址。

我希望OkHttp-client使用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的错误。

2 个答案:

答案 0 :(得分: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);
    }
}

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

尽管问题提交以来已经过去了很长时间,但我希望这个答案能帮助遇到此问题的人们,祝你好运。

答案 1 :(得分:1)

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