为什么我用 OkHttp 得到 java.net.SocketTimeoutException?

Why do I get java.net.SocketTimeoutException with OkHttp?

这是我的配置:

httpClient = new OkHttpClient.Builder()
        .callTimeout(Duration.ofSeconds(60))
        .connectTimeout(Duration.ofSeconds(60))
        .readTimeout(Duration.ofSeconds(60))
        .writeTimeout(Duration.ofSeconds(60))
        .build();

我有一个使用此客户端的多线程进程。 几秒后 运行 我得到:

java.net.SocketTimeoutException: timeout
at okio.Okio.newTimeoutException(Okio.java:232)
at okio.AsyncTimeout.exit(AsyncTimeout.java:286)
at okio.AsyncTimeout.read(AsyncTimeout.java:241)
at okio.RealBufferedSource.indexOf(RealBufferedSource.java:358)

如果我将超时设置为 60 秒怎么可能?

编辑:
即使添加自定义调度程序也无济于事:

Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(Integer.MAX_VALUE);
dispatcher.setMaxRequestsPerHost(Integer.MAX_VALUE);

技术细节:
与我所说的相反,我是 运行 Linux 台机器上的客户端和服务器:

Client's machine: net.ipv4.tcp_keepalive_time = 7200
Server's machine: net.ipv4.tcp_keepalive_time = 7200

您很可能受到操作系统强加的套接字超时的影响。 Java 无法扩展它们,因为套接字由系统管理。根据 this great answer, "Changing TCP Timeouts" section:

Unfortunately since TCP connections are managed on the OS level, Java does not support configuring timeouts on a per-socket level such as in java.net.Socket. I have found some attempts to use Java Native Interface (JNI) to create Java sockets that call native code to configure these options, but none appear to have widespread community adoption or support.

对于 MacOS,您必须查看 sysctl net.inet.tcp 输出并查看系统限制是什么。

我认为问题出在其他地方。超时确实有效。我在我的机器上尝试了以下操作,它工作了 30 秒

package test;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 *
 * @author jingged
 */
public class Test {
OkHttpClient httpClient = null;
     void initClient(){
        httpClient = new OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Test test = new Test();
        test.initClient();
        for(int i = 0;i<10;i++){
            new Thread(new Runnable(){
                @Override
                public void run() {
                   Request request = new Request.Builder().url("https://10.255.255.1").build();
                   Instant start = Instant.now();
                   try (Response response = test.httpClient.newCall(request).execute()) {
                        String s =  response.body().string();
                        System.out.println(s);
                    } catch (IOException ex) {
                        if (ex instanceof java.net.SocketTimeoutException){
                            System.err.print("Socket TimeOut");
                        }else{
                            ex.printStackTrace();
                        }
                    }
                   Instant end = Instant.now();
                    Duration interval = Duration.between(start, end);
                    System.out.println("Execution time " + interval.getSeconds()+" seconds");
                }

            }).start();
        }
    }
}

输出:

Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Execution time 30 seconds
Socket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOutSocket TimeOut
BUILD SUCCESSFUL (total time: 32 seconds)

试试上面的代码。上面代码中提到的 url 会产生 TimeOut 异常,因为它不存在,但超时时间应该保持 30 秒。 顺便说一句,我在 MAC OS High Sierra 上用 Java 8 和 netbeans 8.2 用 okhttp3

测试了这个