Read timed out
就是已经连接成功,但是服务器没有及时返回数据,导致读超时。
java程序模拟Read timed out
服务端
public class SimpleServer {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket serverSocket = new ServerSocket(8888,200);
Thread.sleep(6666666);
}
}
//客户端程序
@Test
public void testReadTimeOut() {
Socket socket = new Socket();
long startTime = 0;
try {
socket.connect(new InetSocketAddress("127.0.0.1", 8888), 10000);
System.out.println("socket连接成功....");
socket.setSoTimeout(2000);
startTime = System.currentTimeMillis();
int read = socket.getInputStream().read();
} catch (IOException e) {
e.printStackTrace();
} finally {
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
}
执行结果
socket连接成功....
执行时间:2008
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)
at java.net.SocketInputStream.read(SocketInputStream.java:223)
at cn.jannal.net.port.SimpleClient.testReadTimeOut(SimpleClient.java:29)
模拟超时Read timed out
,PSH
的意思是控制信息是可以正常传送的,也就是说握手是正常成功的,然后传输数据的时候,我们限制了服务器无法给客户端传送数据内容,与上面java程序模拟的原理类似,都是可以建立连接,但是不返回数据。
防火墙增加-A OUTPUT -p tcp -m tcp --tcp-flags PSH PSH --sport 8888 -j DROP
执行流程
client socket
socket.setSoTimeout(int timeout)
--> socketOptions.setOption(int optID, Object value)
--> AbstractPlainSocketImpl.setOption(int opt, Object val)
--> PlainSocketImpl.socketSetOption(int cmd, boolean on, Object value)
--> PlainSocketImpl.c 中的Java_java_net_PlainSocketImpl_socketSetOption
从以下代码分析可以看出,socket.setSoTimeout(int timeout)
选项在Solaris/linux
下根本就没有调用C库函数中的setSockOpt(SO_RCVTIMEO)
,那java中到底是怎么实现read timed out
的呢?
/*
* Class: java_net_PlainSocketImpl
* Method: socketSetOption
* Signature: (IZLjava/lang/Object;)V
*/
JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this,
jint cmd, jboolean on,
jobject value) {
int fd;
int level, optname, optlen;
union {
int i;
struct linger ling;
} optval;
/*
* Check that socket hasn't been closed
*/
fd = getFD(env, this);
if (fd < 0) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
return;
}
/*
* SO_TIMEOUT is a NOOP on Solaris/Linux
* ,在Solaris/linux下根本就没有调用C库函数中的setSockOpt(SO_RCVTIMEO)
*/
if (cmd == java_net_SocketOptions_SO_TIMEOUT) {
return;
}
/*
* Map the Java level socket option to the platform specific
* level and option name.
*/
if (NET_MapSocketOption(cmd, &level, &optname)) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid option");
return;
}
switch (cmd) {
case java_net_SocketOptions_SO_SNDBUF :
case java_net_SocketOptions_SO_RCVBUF :
case java_net_SocketOptions_SO_LINGER :
case java_net_SocketOptions_IP_TOS :
{
jclass cls;
jfieldID fid;
cls = (*env)->FindClass(env, "java/lang/Integer");
CHECK_NULL(cls);
fid = (*env)->GetFieldID(env, cls, "value", "I");
CHECK_NULL(fid);
if (cmd == java_net_SocketOptions_SO_LINGER) {
if (on) {
optval.ling.l_onoff = 1;
optval.ling.l_linger = (*env)->GetIntField(env, value, fid);
} else {
optval.ling.l_onoff = 0;
optval.ling.l_linger = 0;
}
optlen = sizeof(optval.ling);
} else {
optval.i = (*env)->GetIntField(env, value, fid);
optlen = sizeof(optval.i);
}
break;
}
/* Boolean -> int */
default :
optval.i = (on ? 1 : 0);
optlen = sizeof(optval.i);
}
if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) {
#ifdef __solaris__
if (errno == EINVAL) {
// On Solaris setsockopt will set errno to EINVAL if the socket
// is closed. The default error message is then confusing
char fullMsg[128];
jio_snprintf(fullMsg, sizeof(fullMsg), "Invalid option or socket reset by remote peer");
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg);
return;
}
#endif /* __solaris__ */
NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
"Error setting socket option");
}
}
再看java代码中读取数据
InputStream inputStream = socket.getInputStream();
inputStream.read()
这里inputStream
的实现类是socketInputStream
。执行流程如下,超时设置的值是在read()
方法被调用的时候传入的
-->read() ①
--> read(b, off, length, impl.getTimeout());//最终调用的都是这个方法,此时传入SO_TIMEOUT ②
-->native int socketRead0(FileDescriptor fd,
byte b[], int off, int len,
int timeout) ③
--> SocketInputStream.c中的Java_java_net_SocketInputStream_socketRead0 ④
从下面代码分析可知,java中setSoTimeout()
设置值最终是传给了linux下的select()
函数,并没有通过C语言的socket选项
来设置。只要在read()
之前设置超时时间即可,计算超时的核心代码在NET_Timeout
中
/*
* Class: java_net_SocketInputStream
* Method: socketRead0
* Signature: (Ljava/io/FileDescriptor;[BIII)I
*/
JNIEXPORT jint JNICALL
Java_java_net_SocketInputStream_socketRead0(JNIEnv *env, jobject this,
jobject fdObj, jbyteArray data,
jint off, jint len, jint timeout)
{
char BUF[MAX_BUFFER_LEN];
char *bufP;
jint fd, nread;
if (IS_NULL(fdObj)) {
/* shouldn't this be a NullPointerException? -br */
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
return -1;
} else {
fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
/* Bug 4086704 - If the Socket associated with this file descriptor
* was closed (sysCloseFD), then the file descriptor is set to -1.
*/
if (fd == -1) {
JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
return -1;
}
}
/*
* If the read is greater than our stack allocated buffer then
* we allocate from the heap (up to a limit)
*/
if (len > MAX_BUFFER_LEN) {
if (len > MAX_HEAP_BUFFER_LEN) {
len = MAX_HEAP_BUFFER_LEN;
}
bufP = (char *)malloc((size_t)len);
if (bufP == NULL) {
bufP = BUF;
len = MAX_BUFFER_LEN;
}
} else {
bufP = BUF;
}
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;
}
}
nread = NET_Read(fd, bufP, len);
if (nread <= 0) {
if (nread < 0) {
switch (errno) {
case ECONNRESET:
case EPIPE:
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
break;
case EBADF:
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
break;
case EINTR:
JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
"Operation interrupted");
break;
default:
NET_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException", "Read failed");
}
}
} else {
(*env)->SetByteArrayRegion(env, data, off, nread, (jbyte *)bufP);
}
if (bufP != BUF) {
free(bufP);
}
return nread;
}
NET_timeout
代码是对select(s, timeout)
的封装,使用select
函数实现定时器,来判断是否超过设置的时间(setSoTimeout()
)
/*
* Wrapper for select(s, timeout). We are using select() on Mac OS due to Bug 7131399.
* Auto restarts with adjusted timeout if interrupted by
* signal other than our wakeup signal.
*/
int NET_Timeout(int s, long timeout) {
long prevtime = 0, newtime;
struct timeval t, *tp = &t;
fd_set fds;
fd_set* fdsp = NULL;
int allocated = 0;
threadEntry_t self;
fdEntry_t *fdEntry = getFdEntry(s);
/*
* Check that fd hasn't been closed.
*/
if (fdEntry == NULL) {
errno = EBADF;
return -1;
}
/*
* Pick up current time as may need to adjust timeout
*/
if (timeout > 0) {
/* Timed */
struct timeval now;
gettimeofday(&now, NULL);
prevtime = now.tv_sec * 1000 + now.tv_usec / 1000;
t.tv_sec = timeout / 1000;
t.tv_usec = (timeout % 1000) * 1000;
} else if (timeout < 0) {
/* Blocking */
tp = 0;
} else {
/* Poll */
t.tv_sec = 0;
t.tv_usec = 0;
}
if (s < FD_SETSIZE) {
fdsp = &fds;
FD_ZERO(fdsp);
} else {
int length = (howmany(s+1, NFDBITS)) * sizeof(int);
fdsp = (fd_set *) calloc(1, length);
if (fdsp == NULL) {
return -1; // errno will be set to ENOMEM
}
allocated = 1;
}
FD_SET(s, fdsp);
for(;;) {
int rv;
/*
* call select on the fd. If interrupted by our wakeup signal
* errno will be set to EBADF.
*/
startOp(fdEntry, &self);
rv = select(s+1, fdsp, 0, 0, tp);
endOp(fdEntry, &self);
/*
* If interrupted then adjust timeout. If timeout
* has expired return 0 (indicating timeout expired).
*/
if (rv < 0 && errno == EINTR) {
if (timeout > 0) {
struct timeval now;
gettimeofday(&now, NULL);
newtime = now.tv_sec * 1000 + now.tv_usec / 1000;
timeout -= newtime - prevtime;
if (timeout <= 0) {
if (allocated != 0)
free(fdsp);
//返回0表示超时
return 0;
}
prevtime = newtime;
t.tv_sec = timeout / 1000;
t.tv_usec = (timeout % 1000) * 1000;
}
} else {
if (allocated != 0)
free(fdsp);
return rv;
}
}
}
Read timed out
表示已经连接成功(即三次握手已经完成),但是服务器没有及时返回数据(没有在设定的时间内返回数据),导致读超时。Read timed out
并不是通过C函数setSockOpt(SO_RCVTIMEO)
来设置的,而是通过select(s, timeout).
来实现定时器,并抛出JNI
异常来控制的java socket
读超时的设置是在read()
方法被调用的时候传入的,所以只要在read()
调用之前设置即可