8

プロジェクトで内部HttpServerクラスを使用して、HTTP を介してクライアントとサーバー間でデータを交換しています。Java 7 に切り替えたときに、結果の配信が遅れていることに気付きました。問題を次のサンプルに減らすことができます。

クラスは、リクエストごとに現在の日付とリクエスト URI を単純に返すEchoServerコンテキストを作成します。/echoこのサービスは、ループ内のクライアントによって呼び出されます。

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Date;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class EchoServer {

    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(80), 0);
        server.createContext("/echo", new EchoHandler());
        server.start();
    }

    static class EchoHandler implements HttpHandler {
        public void handle(HttpExchange httpExchange) throws IOException {
            httpExchange.getResponseHeaders().add("Content-type", "text/html");
            String response = "<b>" + new Date() + "</b> for "  + httpExchange.getRequestURI();
            httpExchange.sendResponseHeaders(200, response.length());
            OutputStream os = httpExchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}

次のクライアントは、クラスを使用して無限ループでサービスを呼び出し、URL返されたストリームから最初の文字 (<記号) を出力します。さらに、クライアントは現在の時刻を出力します。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

public class EchoClient {

    public static void main(String[] args) throws Exception{
        while(true) {
            URL url = new URL("http://localhost:80/echo");

            BufferedReader rd = new BufferedReader(new InputStreamReader(url.openStream()));
            int res = rd.read();
            System.out.println((char)res);
            System.out.println(System.currentTimeMillis());
        }
    }
}

このコードを Java6 で実行すると、すべてが正常に機能し、結果が約 1 秒で出力されます。5ミリ秒ごと。

% java -version
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02, mixed mode)

% java EchoClient
<
1362515635677
<
1362515635682
<
1362515635687
<
1362515635691

コードが Java7 で実行される場合、各リクエストは約 1000 ミリ秒を使用します。

% java -version
java version "1.7.0_17"
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)

% java EchoClient
<
1362517297845
<
1362517298844
<
1362517299845
<
1362517300845

1000msのタイムアウトがどこかに隠されているようです。InputStreamReader文字がではなくで読み取られる場合BufferedReader、同じ遅延が発生します。バイトが入力ストリームから直接読み取られる場合、遅延は見られません。一方、プログラムがサーブレットに対して実行される場合、または が使用されているEchoClientかどうかに関係なく、すべてが正常に機能します。BufferedReaderInputStreamReader

そのクラスInputStreamReaderは、HttpServer の Java 7 実装によって提供されなくなったサーバーから何かを期待しているようです。ここで正確に何が起こっているのか、この問題をどのように解決できるのか考えていますか? 回避策はありますか?それともこれはバグですか?

ありがとう!


クライアント コードにさらにタイミングを追加しました。

public static void main(String[] args) throws Exception{
    while(true) {
        System.out.println("0: "+System.currentTimeMillis());
        URL url = new URL("http://localhost:80/echo");
        System.out.println("1: "+System.currentTimeMillis());
        InputStream in = url.openStream();
        System.out.println("2: "+System.currentTimeMillis());
        InputStreamReader isr = new InputStreamReader(in);
        System.out.println("3: "+System.currentTimeMillis());
        char res = (char)isr.read(); // character read is `<`
        System.out.println(res + ": "+System.currentTimeMillis());
    }
}

次の結果が得られます。

% java EchoClient
0: 1362532555535
1: 1362532555537
2: 1362532555608
3: 1362532555609
<: 1362532555611
0: 1362532555612
1: 1362532555613
2: 1362532556608
3: 1362532556609
<: 1362532556610
0: 1362532556611
1: 1362532556612
2: 1362532557609
3: 1362532557610
<: 1362532557611
0: 1362532557612
1: 1362532557613

の最初の呼び出しにopenStreamはある程度の時間がかかります (70 ミリ秒) が、それ以降のすべての呼び出しにはさらに時間がかかりますopenStream(約 996 ミリ秒)。

4

4 に答える 4

1

私は同じ問題を経験しましたが、user1050755 のコメントはバグが満たされたと指摘しており、解決策が 1 つあります。

...サーバーがスレッドプールを使用している場合、これは問題ではありませんが、シングル スレッド サーバーの場合、このタイムアウトがボトルネックになります。

したがって、マルチスレッド サーバーを作成します。

        final Executor multi = Executors.newFixedThreadPool(10);
        final HttpServer server = HttpServer.create(new InetSocketAddress(s_HTTP_PORT), 5);
        //... do your REST bindings here
        server.setExecutor(multi);
        server.start();

私にとって魅力のように働きました。

PS。「com.sun.net.httpserver はひどい」などのコメントは何の助けにもなりません。「代わりに Apache を使用してください」と同じです。

于 2015-10-20T20:44:18.117 に答える
1

によって返された BufferedReader または InputStream を閉じていないようですurl.openStream()。ストリームを閉じないと、後続の反復で接続を再利用できるという問題が発生する可能性があります (一般的にバグのある動作です)。

と を明示的に呼び出すと、結果が異なりますrd.close()stream.close()?

于 2013-03-05T22:17:14.147 に答える
1

Oracleにバグレポートを提出しました。ここでは、両方の Java リリース (SE 6 または 7) で 38 ミリ秒の遅延が発生しています。

/**
 * @test
 * @bug 
 * @summary  pipelining delay on Ubuntu 12.04.01 LTS / amd64
 */

import com.sun.net.httpserver.*;

import java.util.*;
import java.util.concurrent.*;
import java.io.*;
import java.net.*;

public class Bug {

    static int iterations = 20;
    static long requiredMinimumDelay = 10L;

    public static void main (String[] args) throws Exception {
        Handler handler = new Handler();
        InetSocketAddress addr = new InetSocketAddress (0);
        HttpServer server = HttpServer.create (addr, 0);
        HttpContext ctx = server.createContext ("/test", handler);
        ExecutorService executor = Executors.newCachedThreadPool();
        server.setExecutor (executor);
        server.start ();

        long minDelay = requiredMinimumDelay * 1000L;

        try {
            for(int i = 0; i < iterations; i++) {
                URL url = new URL ("http://localhost:"+server.getAddress().getPort()+"/test/foo.html");
                HttpURLConnection urlc = (HttpURLConnection)url.openConnection ();
                InputStream is = urlc.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String res = br.readLine();
                br.close();

                // skip first few
                if(i < iterations/2) {
                    continue;
                }

                long delay = System.currentTimeMillis() - Long.parseLong(res);
                System.out.println("delay: "+delay+" ms");
                if(delay < minDelay) {
                    minDelay = delay;
                }
            }
        } catch (Exception ex) {}

        server.stop(2);
        executor.shutdown();

        if(minDelay > requiredMinimumDelay) {
            throw new Exception("minimum delay too large: "+minDelay);
        }
    }

    static class Handler implements HttpHandler {
        public void handle (HttpExchange t)
            throws IOException
        {
            InputStream is = t.getRequestBody();
            Headers map = t.getRequestHeaders();
            Headers rmap = t.getResponseHeaders();
            while (is.read () != -1) ;
            is.close();
            String response = Long.toString(System.currentTimeMillis())+"\n";
            t.sendResponseHeaders (200, response.length());
            OutputStream os = t.getResponseBody();
            os.write (response.getBytes());
            t.close();
        }
    }    
}

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8009548

更新: Oracle はそれを「2 つの異なるバグ」として分類したことがわかりました。

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014254

したがって、リンクされたバックポートに基づいて、バージョン「8b91」および「7u85」で 1000ms が修正されることを願っています。

于 2013-03-06T14:23:04.923 に答える
0

回避策 (最初は user1050755 から) は、sendResponseHeaders() メソッドの前にこれを追加しています。

 httpExchange.getResponseHeaders().add("Connection", "close");

これは基本的に「キープアライブ」機能を無効にしますが、少なくとも私にとっては、JRE を簡単にアップグレードするオプションがないため、リクエストごとに 1000 ミリ秒から 50 ミリ秒になりました。「キープアライブ」機能は失われますが、FWIW.

于 2015-08-06T17:51:25.580 に答える