2015-03-18 21 views
6

Zdaję sobie bardzo dziwny problem z klasą Scanner. Używam Scanner do czytania wiadomości od Socket ze specjalnym tokenem EOF. Wszystko działa poprawnie, jeśli klient zapisuje wszystkie żądania naraz lub żądania mają dane, ale operacja blokowania zatrzymuje się na serwerze, a z kolei na kliencie, gdy wiadomości są zapisywane w porcjach, a następny token powinien być pustym ciągiem .java.util.Scanner zawiesza się na hasNext()

Co by to spowodowało? Jak mogę tego uniknąć?

Oto uproszczona wersja tego, co próbuję zrobić, \n jest używany do celów testowych, załóżmy, że ogranicznik może być dowolnym ciągiem znaków.

Kod serwera:

ServerSocketChannel serverChannel = null; 
try { 
    serverChannel = ServerSocketChannel.open(); 

    ServerSocket serverSocket = serverChannel.socket(); 
    serverSocket.bind(new InetSocketAddress(9081)); 

    SocketChannel channel = serverChannel.accept(); 
    Socket socket = channel.socket(); 

    InputStream is = socket.getInputStream(); 
    Reader reader = new InputStreamReader(is); 
    Scanner scanner = new Scanner(reader); 
    scanner.useDelimiter("\n"); 

    OutputStream os = socket.getOutputStream(); 
    Writer writer = new OutputStreamWriter(os); 

    while (scanner.hasNext()) { 
     String msg = scanner.next(); 
     writer.write(msg); 
     writer.write('\n'); 
     writer.flush(); 
    } 
} catch (IOException e) { 
    e.printStackTrace(); 
} finally { 
    if (serverChannel != null) { 
     try { 
      serverChannel.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

Client robocza:

Socket socket = new Socket(); 
try { 
    socket.connect(new InetSocketAddress("localhost", 9081)); 

    InputStream is = socket.getInputStream(); 
    Reader reader = new InputStreamReader(is); 
    Scanner scanner = new Scanner(reader); 
    scanner.useDelimiter("\n"); 

    OutputStream os = socket.getOutputStream(); 
    Writer writer = new OutputStreamWriter(os); 

    writer.write("foo\n\nbar\n"); 
    writer.flush(); 
    System.out.println(scanner.next()); 
    System.out.println(scanner.next()); 
    System.out.println(scanner.next()); 

} catch (IOException e) { 
    e.printStackTrace(); 
} finally { 
    try { 
     socket.close(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

schowek Klient:

Socket socket = new Socket(); 
try { 
    socket.connect(new InetSocketAddress("localhost", 9081)); 

    InputStream is = socket.getInputStream(); 
    Reader reader = new InputStreamReader(is); 
    Scanner scanner = new Scanner(reader); 
    scanner.useDelimiter("\n"); 

    OutputStream os = socket.getOutputStream(); 
    Writer writer = new OutputStreamWriter(os); 

    writer.write("foo\n"); 
    writer.flush(); 
    System.out.println(scanner.next()); 

    writer.write("\n"); 
    writer.flush(); 
    System.out.println(scanner.next()); 

    writer.write("bar\n"); 
    writer.flush(); 
    System.out.println(scanner.next()); 

} catch (IOException e) { 
    e.printStackTrace(); 
} finally { 
    try { 
     socket.close(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

Odpowiedz

0

Nie zamykamy przyjętego gniazda.

Nie potrzebujesz "specjalnego tokena EOF". Koniec strumienia jest jednoznaczny.

+0

Mam zamykania kanału wejściowego gniazda mimo wszystko został odczytany, to domyślnie zamyka gniazdo podstawowy (s) , niezależnie od tego, czy program się zawiesza na długo przed zamknięciem gniazda. Token jest używany jako ogranicznik wiadomości, więc wiele wiadomości może zostać wysłanych podczas pojedynczej sesji, nie chodzi tu o wykrycie końca strumienia.Jestem świadomy, że koniec strumienia spowodowałby powrót funkcji hasNext(). –

+0

Należy pamiętać, że jest to tylko przykład do odtworzenia problemu, kod produkcyjny faktycznie wykorzystuje niezablokowany kanał gniazd, stale akceptuje połączenia i spawns wątki robocze do obsługi odczytu, zapisu i zamykania gniazd. –

0

Spędziłem trochę czasu na śledzeniu kodu, a problem jest zdecydowanie wadą klasy Scanner.

public boolean hasNext() { 
    ensureOpen(); 
    saveState(); 
    while (!sourceClosed) { 
     if (hasTokenInBuffer()) 
      return revertState(true); 
     readInput(); 
    } 
    boolean result = hasTokenInBuffer(); 
    return revertState(result); 
} 

hasNext() rozmowy hasTokenInBuffer()

private boolean hasTokenInBuffer() { 
    matchValid = false; 
    matcher.usePattern(delimPattern); 
    matcher.region(position, buf.limit()); 

    // Skip delims first 
    if (matcher.lookingAt()) 
     position = matcher.end(); 

    // If we are sitting at the end, no more tokens in buffer 
    if (position == buf.limit()) 
     return false; 

    return true; 
} 

hasTokenInBuffer() zawsze pomija pierwszy ogranicznik jeśli istnieje, jak wyjaśniono w javadoc.

Kolejnym() i hasNext() Metody i sposoby ich towarzyszące prymitywny typu (takie jak nextInt() i hasNextInt()) najpierw przejść żadnych danych odpowiadający wzór ogranicznika, a następnie spróbuj powrotu następnego znak. Oba mająNastępne i następne metody mogą blokować oczekiwanie na dalsze wprowadzanie. Określa, czy bloki metod hasNext nie mają związku z tym, czy zablokowana zostanie powiązana z nimi następna metoda.

Najpierw pominąć token, który był jeszcze w buforze od ostatniego wniosku, wówczas zauważymy, że nie mamy żadnych nowych danych w naszym buforem tak nazywamy readInput(), w tym przypadku po prostu \n, wtedy pętla wróć na numer hasTokenInBuffer(), który ponownie omija nasz ogranicznik!

W tym momencie serwer czeka na więcej danych wejściowych, a klient czeka na odpowiedź. Impas.

ten można łatwo uniknąć, jeśli sprawdzamy czy pominęliśmy ostatni żeton ...

private boolean skippedLast = false; 

private boolean hasTokenInBuffer() { 
    matchValid = false; 
    matcher.usePattern(delimPattern); 
    matcher.region(position, buf.limit()); 

    // Skip delims first 
    if (!skippedLast && matcher.lookingAt()) { 
     skippedLast = true; 
     position = matcher.end(); 
    } else { 
     skippedLast = false; 
    } 

    // If we are sitting at the end, no more tokens in buffer 
    if (position == buf.limit()) 
     return false; 

    return true; 
} 
Powiązane problemy