我正在使用Java Sockets。我有这行代码:
Socket webSocket = new Socket();
webSocket.connect(new InetSocketAddress(domain, 80), 120000);
指定的超时时间为120000ms(2分钟)。我很好奇这个超时是否真的被遵守,或者它是否被限制在平台默认的连接超时值。另外,如何检查平台的默认连接超时值?换句话说,当我调用这段代码时,超时是什么:
Socket webSocket = new Socket(domain, 80);
首先,这个超时是否依赖于平台?我知道有SO_TIMEOUT
,但我认为它只影响read()
超时,而不影响connect()
。
我认为有一个默认超时,因为不指定超时值仍然会导致Connection timed out: connect
进一步调查,您会发现java连接超时和系统级连接超时之间有细微的区别。
通过改变行:
webSocket.connect(new InetSocketAddress(domain, 80), 120000);
转换成:
webSocket.connect(new InetSocketAddress(domain, 80), 10);
你会看到你得到一个完全不同的异常:java.net.SocketTimeoutException
而不是通常的java.net.ConnectException
。
对Socket.connect()
的实际调用实际上被延迟到SocketImpl
抽象类,这样确切的机制是实现定义的。如Socket
中connect()
方法的摘录所示:
...
if (!oldImpl)
impl.connect(epoint, timeout);
else if (timeout == 0) {
if (epoint.isUnresolved())
impl.connect(addr.getHostName(), port);
else
impl.connect(addr, port);
...
基本上,第二个异常(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)
请注意,异常实际上是从本机方法传播的,即从本节(由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.
}
:
rv = connect(fd, (struct sockaddr *)&sa, sa_len);
实际上是对Winsock2连接函数的调用,其MSDN页面可以在这里查看。Winsock2套接字本身已被设置为默认套接字(setsockopt
中的optval
为零)。这意味着它继承了发送和接收的任何系统默认超时。如果连接尝试超过默认超时值,则将SOCKET_ERROR
放入rv
中,导致以下行:
NET_ThrowNew(env, err, "connect");
运行,将ConnectException
传播到堆栈上。(如果你不相信我,请查看OpenJDK源代码中的DualStackPlainSocketImpl.c
)。
嗯…java设置的超时到哪里去了?答案是在方法socketConnect()
下的文件DualStackPlainSocketImpl.java
中,在转换到本机层之前向上一个堆栈级别,特别是这个片段:
configureBlocking(nativefd, false);
try {
connectResult = connect0(nativefd, address, port);
if (connectResult == WOULDBLOCK) {
waitForConnect(nativefd, timeout);
}
} finally {
configureBlocking(nativefd, true);
}
其中connect0
是对本机函数的实际调用。
本质上,当java出现有限的非零超时时,它以异步模式启动本机套接字连接,并等待超时到期或本机方法完成,以先发生的为准。设置较大超时值的问题是,本机连接函数本身可能超时并抛出ConnectException
而不是通常的SocketTimeoutException
。(在我的Windows 10系统下使用Java 8)。
可以在不同的系统上进行相同的练习,以确定套接字超时工作的确切机制。
tl;博士:
共识是基本上所有的套接字操作都是实现定义的。甚至从第一个堆栈层开始,你会发现Socket
将它的大部分工作推迟到一些抽象的SocketImpl
,由操作系统实现。因此,操作系统完全有可能(如上所述)具有更长、更短或完全不存在的超时。从某种意义上说,java超时只有在小于操作系统默认超时(如果有的话)时才会正常工作,所以你应该准备好处理这两种情况(操作系统抛出异常是由于它的本地超时vs java特定的超时到期)。
默认的套接字超时为0,这意味着永远不会超时。如果它在x分钟后超时,这意味着它已经在代码中设置了。在你的例子中是2分钟