写点什么

浅谈 Java 中的 TCP 超时

发布于: 2021 年 06 月 08 日

背景

在远程调用的世界里,Timeout 的情况非常常见,几乎每段时间就会听到几个同事关于 Timeout 各种情况的讨论,偶尔的会出现不同开发语言间的同事的讨论,例如 read timeout, 语言的隔阂使得大家讨论的都不知道是否是同一回事。


对于 Java,各种远程调用,http,hessian,dubbo 什么的,抛个 timeout 异常也是常见的事情,timeout 是什么,一般追追源码,追到最后发现是个 native 方法,看着 javadoc, 了解得不甚透彻。 所以本文尽量从 Java 到操作系统层面尝试说明常见的各种 Timeout。

主要内容

现象

对于 Java 开发来说,最常见的异常莫过于 SocketTimeoutException,从异常日志,一般会有两种情况


  • connect timed out

  • read timed out


Caused by: java.net.SocketTimeoutException: connect timed out        at java.net.PlainSocketImpl.socketConnect(Native Method)        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)        at java.net.Socket.connect(Socket.java:589)
java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:170) at java.net.SocketInputStream.read(SocketInputStream.java:141)
复制代码

原理 connect timed out

"connect timed out"从字面上看就是连接的超时时间,那么超时时间是怎么控制的?

java.net.Socket


从 Sokcet 的 connect 方法可以看出,timeout 参数会一致往下传递,最后到了 PlainSocketImpl.socketConnect 的 native 方法, java native 方法是否真的很神秘?也不神秘,让我们一起看下 JVM 底层的实现,以下是 jdk8-openjdk 的源码

PlainSocketImpl.c


以下只截取部分重要的源码, 从源码上看,没设置超时时间时,jvm 采用 connect 的传统阻塞式方式,反之,则采用 select/poll 非阻塞式的方式, 由于 poll/select 都是得采用轮询的方式,在客户端没有设置超时的时候,采用轮询会带来不必要的开销,所以没设置超时时采用 connect 的阻塞方式是合理的


JNIEXPORT void JNICALLJava_java_net_PlainSocketImpl_socketConnect(JNIEnv *env, jobject this,                                            jobject iaObj, jint port,                                            jint timeout){
if (timeout < 0 ) { connect_rv = NET_Connect(fd, (struct sockaddr *)&him, len); ...}else { #ifndef USE_SELECT { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT;
errno = 0; connect_rv = NET_Poll(&pfd, 1, timeout); }#else { fd_set wr, ex; struct timeval t;
t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000;
FD_ZERO(&wr); FD_SET(fd, &wr); FD_ZERO(&ex); FD_SET(fd, &ex);
errno = 0; connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t); }#endif
}
if (connect_rv == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "connect timed out");
/* * Timeout out but connection may still be established. * At the high level it should be closed immediately but * just in case we make the socket blocking again and * shutdown input & output. */ SET_BLOCKING(fd); JVM_SocketShutdown(fd, 2); return; }
/* has connection been established */ optlen = sizeof(connect_rv); if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv, &optlen) <0) { connect_rv = errno; } } }
复制代码

原理 Read timed out

从下面这个时序图看, read timedout 的原理就是通过系统调用 poll, 传入对应的 socket 文件句柄,在 timeout 时间内没有数据返回


当调用 NET_Timeout 没返回任何数据的时候, 根据情况会抛出 SocketTimeoutException 或者 SokcetException, 这个 SocketTimeoutException 就是我们经常遇到的 read timed out


if (timeout) {    nread = NET_Timeout(fd, timeout);    if (nread <= 0) {        if (nread == 0) {            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",                        "Read timed out");        } else if (nread == JVM_IO_ERR) {            if (errno == EBADF) {                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");             } else {                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",                                              "select/poll failed");             }        } else if (nread == JVM_IO_INTR) {            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",                        "Operation interrupted");        }        if (bufP != BUF) {            free(bufP);        }        return -1;    }}
复制代码

总结

  1. "connect timed out" 是在指定时间内 TCP 连接未创建成功时 jdk 抛出的异常

  2. "Read timed out"是在调用 socketread 后,指定时间内未收到响应时 jdk 抛出的异常, 假如一个 http 响应 10k, 每次 socket read 4k, 那么就需要发起 3 次 read 的请求,假如 timeout 设置 3 秒,那么就允许每次 read 都等待 3 秒,最差的情况就是大概 9 秒读完数据,当然这得是极端的网络情况, 所以大部分情况下都是客户端发起请求后,在指定时间内收到的服务器的回包响应。


用户头像

还未添加个人签名 2018.11.19 加入

还未添加个人简介

评论

发布
暂无评论
浅谈Java中的TCP超时