SocketノートのRead timed outの深い分析

36667 ワード

文書ディレクトリ
  • ReadTimedOut深い分析
  • PlainSocketImpl.c
  • Java_java_net_PlainSocketImpl_socketSetOption
  • read()時
  • Java_java_net_SocketInputStream_socketRead0
  • solaris/native/java/net/bsd_close.c
  • まとめ
  • ReadTimedOut分析
  • 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 outPSHは、制御情報が正常に転送されることを意味します.つまり、握手は正常に成功し、データを転送する際に、サーバがクライアントにデータコンテンツを転送できないことを制限しています.上記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
    
  • 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");
    	    }
    	}
    
    
    

  • read()の場合
  • 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     ④                      
    
    である.
    SocketInputStream SocketInputStream.c ① ② ③ ④ SocketInputStream 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;
    	}
    
    
    
  • である.
    solaris/native/java/net/bsd_close.c
  • 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は、接続に成功した(すなわち、3回の握手が完了した)ことを示すが、サーバがデータをタイムリーに返さず(設定された時間内にデータを返さなかった)、リードタイムアウトを招く.
  • javaのlinuxにおけるRead timed outは、C関数setSockOpt(SO_RCVTIMEO)によって設けるのではなく、select(s, timeout).によってタイマーが実現され、JNIの異常を投げ出すことによって制御される
  • である.
  • java socketのリードタイムアウトの設定はread()メソッドが呼び出されたときに入るので、read()呼び出しの前に設定すれば
  • となる.