Socket笔记之Read timed out深入分析

ReadTimedOut深入分析

  1. Read timed out就是已经连接成功,但是服务器没有及时返回数据,导致读超时。

  2. 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)
    
  3. 模拟超时Read timed outPSH的意思是控制信息是可以正常传送的,也就是说握手是正常成功的,然后传输数据的时候,我们限制了服务器无法给客户端传送数据内容,与上面java程序模拟的原理类似,都是可以建立连接,但是不返回数据。

     防火墙增加-A OUTPUT -p tcp -m tcp --tcp-flags PSH PSH --sport 8888 -j DROP
    
  4. 执行流程

    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
    

PlainSocketImpl.c

Java_java_net_PlainSocketImpl_socketSetOption

  1. 从以下代码分析可以看出,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");
    	    }
    	}
    
    
    

read()

  1. 再看java代码中读取数据

    	InputStream inputStream = socket.getInputStream();
    	inputStream.read()
    
  2. 这里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     ④                      
    
    SocketInputStream SocketInputStream.c SocketInputStream SocketInputStream.c

Java_java_net_SocketInputStream_socketRead0

  1. 从下面代码分析可知,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;
    	}
    
    
    

solaris/native/java/net/bsd_close.c

  1. 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;
            }
    
        }
    }
    

总结

  1. Read timed out表示已经连接成功(即三次握手已经完成),但是服务器没有及时返回数据(没有在设定的时间内返回数据),导致读超时。
  2. java在linux中的 Read timed out并不是通过C函数setSockOpt(SO_RCVTIMEO)来设置的,而是通过select(s, timeout).来实现定时器,并抛出JNI异常来控制的
  3. java socket读超时的设置是在read()方法被调用的时候传入的,所以只要在read()调用之前设置即可
  • 4
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值