自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。