2013-03-05 19 views
7

Używamy wewnętrznej klasy HttpServer w projekcie do wymiany danych między klientem a serwerem za pośrednictwem protokołu HTTP. Po przejściu na platformę Java 7 uzyskaliśmy opóźnienie w dostarczaniu wyników. Możemy zmniejszyć problem do następującej próbki:1s Opóźnienie w HttpServer od Javy 7

Klasa EchoServer tworzy kontekst /echo, który po prostu zwraca bieżącą datę i identyfikator URI żądania po każdym żądaniu. Ta usługa jest następnie wywoływana przez klienta w pętli.

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(); 
     } 
    } 
} 

Poniższy klient wywołuje usługę w nieskończonej pętli przy użyciu klasę URL i drukuje pierwszy znak od zwracanej strumienia (który będzie znakiem <). Ponadto klient drukuje aktualny czas.

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()); 
     } 
    } 
} 

Jeśli ten kod zostanie wykonany na Javie6, wszystko działa poprawnie, a wynik zostanie wydrukowany w przybliżeniu. co 5 ms.

% 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 

Jeśli kod jest wykonywany na Javie7, to każde żądanie używa około 1000ms.

% 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 

Wygląda na to, że limit czasu wynoszący 1000ms jest gdzieś ukryty. Jeśli zamiast tego zostanie odczytany znak InputStreamReader przez BufferedReader, nastąpi to samo opóźnienie. Jeśli bajt jest odczytywany bezpośrednio ze strumienia wejściowego, nie widać opóźnienia. Z drugiej strony, jeśli program EchoClient jest uruchamiany w stosunku do serwletu, wszystko działa poprawnie, niezależnie od tego, czy użyto BufferedReader czy InputStreamReader. Wydaje się, że klasa InputStreamReader oczekuje czegoś od serwera, który nie jest już dostarczany przez implementację Java 7 z HttpServer. Czy masz pojęcie, co dokładnie się tutaj dzieje i jak można rozwiązać ten problem? Obejście? Czy to błąd?

Dzięki!


Dodałem kolejne czasy w kodzie klienckim:

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()); 
    } 
} 

z następującym wynikiem:

% 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 

Pierwsze wezwanie openStream zajmuje trochę czasu (70ms), ale wszystkie dalsze wywołania o wartości openStream trwają znacznie dłużej (około 996ms).

+2

W rzeczywistości, com.sun.net.httpserver nie jest częścią Java API, a Myślę, że nie powinno się tego używać w projektach krytycznych dla misji. Możesz wypróbować alternatywy, takie jak NanoHTTPD: http://elonen.iki.fi/code/nanohttpd/ – gd1

+0

Jest to bardzo interesujące, ale trudno w to uwierzyć. Czy możesz wstawić więcej debugowania sygnatury czasowej, aby znaleźć dokładnie, która część kodu zajmuje 1 sekundę? – irreputable

+1

jest to wywołanie 'url.openStream()', które ma 1000ms, ale tylko w jego drugim (i późniejszym) wywołaniu w pętli, i tylko wtedy, gdy później 'InputStreamReader' jest generowany i używany do odczytu bajta. Tak więc 'BufferedReader' wydaje się być niewinny. – Dominik

Odpowiedz

0

Wygląda na to, że nie zamykasz bufora BufferedReader lub metody InputStream zwróconej przez url.openStream(). Niezamknięcie strumienia może spowodować problemy z ponownym wykorzystaniem połączenia w kolejnych iteracjach (i ogólnie jest to zachowanie błędne).

Czy masz różne wyniki z jawnymi połączeniami do rd.close() i stream.close()?

+0

Wywołanie "rd.close() 'na BufferedReader nie pomaga, a neigher wywołuje' close' na strumieniu wejściowym zwracanym przez URL. – Dominik

1

Właśnie złożył raport o błędach w Oracle. Dostaję tu opóźnienie 38 ms dla obu wersji Java (SE 6 lub 7).

/** 
* @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

Aktualizacja: Okazuje się, Oracle klasyfikuje je jako „dwóch różnych błędów” jeden dla 38ms (które punted na?) I jeden dla 1000ms jeden, którego rozwiązane tutaj:

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

Więc jeden 1000ms nadzieją ustalone w wersji „8b91” i „7u85” na podstawie backports połączonych.

0

Obejście (początkowo z user1050755) jest dodanie tego przed sendResponseHeaders() metody:

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

które zasadniczo uniemożliwia „keep alive” funkcjonalność, ale przynajmniej dla mnie, stało się przejść od 1000ms do 50ms na żądanie, ponieważ nie mam możliwości łatwego uaktualnienia środowiska JRE. Choć traci funkcjonalność FWIW "keep alive".

1

przeżyłem ten sam problem, ale komentarz user1050755 podkreśla bug filled i ma jedno rozwiązanie:

... to nie jest problem, gdy serwer używa puli wątków, ale dla jednego serwer gwintowany, to czas oczekiwania powoduje powstanie wąskiego gardła ..

Więc dokonać wielowątkowy serwer:

 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(); 

pracował jak urok dla mnie.

PS. komentarze takie jak "com.sun.net.httpserver jest okropne" nie dają żadnej pomocy - to jest to samo co "użyj Apache zamiast"

Powiązane problemy