8

我们HttpServer在项目中使用内部类通过 HTTP 在客户端和服务器之间交换数据。当我们切换到 Java 7 时,我们意识到交付结果的延迟。我们可以将问题简化为以下示例:

EchoServer创建上下文,该上下文/echo仅在每个请求时返回当前日期和请求 URI。然后,该服务由客户端循环调用。

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 上执行,则一切正常,并且大约会打印一个结果。每 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

似乎在某处隐藏了 1000 毫秒的超时。InputStreamReader如果字符是在 上而不是在上读取的,则会BufferedReader发生相同的延迟。如果直接从输入流中读取一个字节,则看不到延迟。另一方面,如果EchoClient程序是针对 servlet 运行的,那么一切正常,与是否使用 theBufferedReader或 the无关InputStreamReader

看来,该类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

刚刚向 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

更新:原来甲骨文将其归类为“两个不同的错误”,一个针对 38 毫秒(他们强调?),一个针对 1000 毫秒,他们在这里解决了这个问题:

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

因此,1000ms 希望基于链接的反向端口在版本“8b91”和“7u85”中得到修复。

于 2013-03-06T14:23:04.923 回答
1

您似乎没有关闭 BufferedReader 或由url.openStream(). 不关闭流可能会导致无法在后续迭代中重用连接(并且通常是错误的行为)。

rd.close()显式调用and有不同的结果stream.close()吗?

于 2013-03-05T22:17:14.147 回答
1

我遇到了同样的问题,但是 user1050755 的评论指出了这个错误,它有一个解决方案:

...当服务器使用线程池时这不是问题,但是对于单线程服务器,此超时会导致瓶颈..

因此,制作一个多线程服务器:

        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 回答
0

一种解决方法(最初来自 user1050755)是在 sendResponseHeaders() 方法之前添加这个:

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

这基本上禁用了“保持活动”功能,但至少对我来说,每个请求从 1000 毫秒到 50 毫秒,因为我没有轻松升级我的 JRE 的选项。虽然它确实失去了 FWIW 的“保持活力”功能。

于 2015-08-06T17:51:25.580 回答