套接字连接超时上限

pec*_*eco 0 java sockets

我正在使用 Java 套接字。我有这行代码:

Socket webSocket = new Socket();
webSocket.connect(new InetSocketAddress(domain, 80), 120000);
Run Code Online (Sandbox Code Playgroud)

指定的超时时间为 120000 毫秒(2 分钟)。我很好奇这个超时是否真的得到遵守,或者它是否被限制在平台默认连接超时值。另外,如何查看平台默认的连接超时值?换句话说,当我调用这段代码时超时是多少:

Socket webSocket = new Socket(domain, 80);
Run Code Online (Sandbox Code Playgroud)

首先,这个超时是否依赖于平台?我知道有SO_TIMEOUT,但我认为它只影响read()超时而不影响connect()

我认为有一个默认超时,因为不指定超时值仍然会导致 Connection timed out: connect.

ini*_*mfs 6

进一步研究,您会发现 java 连接超时和系统级连接超时之间存在细微差别。

通过改变线路:

webSocket.connect(new InetSocketAddress(domain, 80), 120000);
Run Code Online (Sandbox Code Playgroud)

像这样的东西:

webSocket.connect(new InetSocketAddress(domain, 80), 10);
Run Code Online (Sandbox Code Playgroud)

您会看到一个完全不同的异常:java.net.SocketTimeoutException而不是通常的java.net.ConnectException.

实际的调用Socket.connect()实际上被推迟到SocketImpl抽象类,以便确切的机制是实现定义的。connect()正如以下方法中的摘录所示Socket

...
if (!oldImpl)
    impl.connect(epoint, timeout);
else if (timeout == 0) {
    if (epoint.isUnresolved())
        impl.connect(addr.getHostName(), port);
    else
        impl.connect(addr, port);
...
Run Code Online (Sandbox Code Playgroud)

从根本上来说,第二个异常 ( ConnectException) 是一种依赖于实现的异常,这意味着操作系统本身具有在设置 java 超时之前达到的最大超时。进一步探索,在检查堆栈跟踪后,ConnectException我们会看到类似这样的内容(在 Windows 计算机上):

Exception in thread "main" java.net.ConnectException: Connection timed out: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)
Run Code Online (Sandbox Code Playgroud)

请注意,异常实际上是从本机方法传播的,即从本节(由 OpenJDK 提供):

rv = connect(fd, (struct sockaddr *)&sa, sa_len);
if (rv == SOCKET_ERROR) {
    int err = WSAGetLastError();
    if (err == WSAEWOULDBLOCK) {
        return java_net_DualStackPlainSocketImpl_WOULDBLOCK;
    } else if (err == WSAEADDRNOTAVAIL) {
        JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException",
            "connect: Address is invalid on local machine, or port is not valid on remote machine");
    } else {
        NET_ThrowNew(env, err, "connect");
    }
    return -1;  // return value not important.
}
Run Code Online (Sandbox Code Playgroud)

该行:

rv = connect(fd, (struct sockaddr *)&sa, sa_len);
Run Code Online (Sandbox Code Playgroud)

实际上是对Winsock2 connect函数的调用,可以在此处查看其MSDN页面。Winsock2 套接字本身已设置为默认套接字(optvalinsetsockopt为零)。这意味着它继承了发送和接收的系统默认超时。如果连接尝试超过默认超时值,SOCKET_ERROR则会将 a 放入其中rv,导致出现以下行:

NET_ThrowNew(env, err, "connect");
Run Code Online (Sandbox Code Playgroud)

运行,将ConnectException备份传播到堆栈上。(如果您不相信我,请查看DualStackPlainSocketImpl.cOpenJDK 源代码)。

嗯...java设置的超时去哪儿了?DualStackPlainSocketImpl.java答案是在方法下的文件中转换到本机层之前向上一层堆栈socketConnect(),特别是这个片段:

configureBlocking(nativefd, false);
try {
    connectResult = connect0(nativefd, address, port);
    if (connectResult == WOULDBLOCK) {
        waitForConnect(nativefd, timeout);
    }
} finally {
    configureBlocking(nativefd, true);
}
Run Code Online (Sandbox Code Playgroud)

其中connect0是对本机函数的实际调用。

本质上,当 java 遇到有限的非零超时时,它会以异步模式启动本机套接字连接,并等待超时到期或本机方法完成(以先发生者为准)。设置较大超时值的问题是本机 connect 函数本身可能会超时并抛出 aConnectException而不是通常的SocketTimeoutException. (在我的 Java 8 下的特定 Windows 10 系统上)。

可以在不同的系统上进行相同的练习,以确定套接字超时工作的确切机制。


长话短说:

共识是基本上所有套接字操作都是实现定义的。即使从第一个堆栈层开始,您也会发现Socket大部分工作都被SocketImpl操作系统实现的一些抽象所推迟。因此,操作系统完全有可能(如上所述)具有更长、更短或完全不存在的超时。从某种意义上说,java 超时只有在比操作系统默认超时(如果有的话)短的情况下才能正常工作,因此您应该准备好处理这两种情况(操作系统由于其本机超时与 java 超时而抛出异常) -特定超时到期)。