自Java 7以来1s延迟了HttpServer

我们在项目中使用内部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 = "" + new Date() + " 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 ,则会发生相同的延迟。 如果直接从输入流中读取一个字节,则无法看到延迟。 另一方面,如果针对servlet运行EchoClient程序,那么一切正常,无论是使用BufferedReader还是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的第一次调用需要一些时间(70ms),但是openStream所有进一步调用需要更长的时间(大约996ms)。

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

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

那么,制作一个multithreading服务器:

  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而不是”相同

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

更新:事实certificate,甲骨文将其归类为“两个不同的错误”,一个用于38毫秒(他们用的是哪个?),一个用于1000毫秒,这是他们在这里解决的:

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

因此1000ms的一个希望固定在版本“8b91”和“7u85”基于后端链接。

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

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

解决方法(最初来自user1050755)在sendResponseHeaders()方法之前添加此方法:

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

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