Java.util.Scanner freezes on hasNext ()

I am encountering a very strange problem with the Scanner class. I use Scanner to read messages from Socket with a special EOF token. Everything works fine, if the client writes all requests at the same time or the requests have data, but the hasNext() blocking operation hangs on the server, and in turn, the client, when messages are written in pieces and the next token, should be an empty line.

What can cause this? How can i avoid this?

Here's a simplified version of what I'm trying to do, \n used for testing purposes, suppose any line can be a delimiter.

Server Code:

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

Working client:

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

Hanging customer:

 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(); } } 
+6
source share
2 answers

You do not close the accepted socket.

You do not need a special EOF token. The end of the stream is unambiguous.

0
source

I spent some time tracking the code, and the problem is certainly a defect in the Scanner class.

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

hasNext() calls 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() always skips the first delimiter if it exists, as described in javadoc.

The next () and hasNext () methods and their primitive type methods (such as nextInt () and hasNextInt ()) first skip any input matching the delimiter pattern, and then try to return the next token. Both hasNext and the following methods can block waiting for further input. Regardless of whether the hasNext block blocks the method, whether its associated next method will be blocked.

First, we will skip the token that was still in the buffer from the last request, then we notice that we do not have new data in our buffer, so we call readInput() , in this case just \n , then we will loop back to hasTokenInBuffer() , which again will skip our delimiter!

At this point, the Server expects more input, and the Client expects a response. Dead end.

This can easily be avoided if we check to see if we missed the last token ...

 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; } 
0
source

Source: https://habr.com/ru/post/983986/


All Articles