2015-03-18 5 views
6

У меня очень странная проблема с классом Scanner. Я использую Scanner для чтения сообщений от Socket со специальным токеном EOF. Все работает нормально, если клиент сразу записывает все запросы или запросы имеют данные, но операция блокировки hasNext() зависает на сервере, а клиент, когда сообщения пишутся в кусках и следующий токен, должен быть пустой строкой ,java.util.Scanner зависает hasnext()

Что может вызвать это? Как я могу избежать этого?

Вот упрощенная версия того, что я пытаюсь сделать, \n используется для тестирования, предположим, что разделителем может быть любая строка.

код сервера:

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

Рабочий Клиент:

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

висячие Клиент:

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

ответ

0

Вы не закрываем принятый сокет.

Вам не нужен специальный маркер EOF. Конец потока недвусмыслен.

+0

Я закрываю канал сокета после ввода всех считаны, это неявно закрывает основной сокет (ы) , независимо от того, какая программа зависает задолго до того, как сокет должен быть закрыт. Токен используется как разделитель сообщений, поэтому несколько сообщений могут быть отправлены в течение одного сеанса, это не значит, что вы обнаруживаете конец потока.Я знаю, что конец потока приведет к возврату функции hasNext(). –

+0

Пожалуйста, помните, что это всего лишь пример для воспроизведения проблемы, производственный код на самом деле использует неблокирующий сокетный канал, постоянно принимает соединения и порождает рабочие потоки для обработки чтения, записи и закрытия сокетов. –

0

Я потратил некоторое время на отслеживание кода, и проблема, безусловно, является дефектом в классе Scanner.

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

hasNext() звонки 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() всегда пропускает первый разделитель, если он существует, как объяснено в Javadoc.

Следующая() и hasNext() методы и методы их компаньоны примитивные типа (такие как nextInt() и hasNextInt()) первый пропустить любой входной сигнал, который соответствует шаблону разделитель, а затем пытаться вернуться на следующий маркер. Оба hasNext и следующие методы могут блокировать ожидание дальнейшего ввода. Независимо от того, блокирует ли блок hasNext метод, будет ли заблокирован его связанный следующий метод.

Сначала мы пропускаем маркер, который был еще в буфере с момента последнего запроса, то мы замечаем, что мы не имеем какие-либо новые данные в нашем буфере, поэтому мы называем readInput(), в этом случае просто \n, то цикл назад к hasTokenInBuffer(), который снова пропускает наш разделитель!

На этом этапе Сервер ожидает большего ввода, и Клиент ожидает ответа. Тупик.

Этого можно легко избежать, если мы проверяем, если мы пропустили последний маркер ...

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; 
} 
Смежные вопросы