我在Scanner
类中遇到了一个非常奇怪的问题。我使用Scanner
从带有特殊EOF令牌的Socket
中读取消息。如果客户端一次写入所有请求,或者请求有数据,那么一切都很好,但当消息以块的形式写入,并且下一个令牌应该是空字符串时,阻塞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("foonnbarn");
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("foon");
writer.flush();
System.out.println(scanner.next());
writer.write("n");
writer.flush();
System.out.println(scanner.next());
writer.write("barn");
writer.flush();
System.out.println(scanner.next());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
我花了一些时间跟踪代码,问题肯定是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;
}
如果第一个分隔符如javadoc中所述存在,hasTokenInBuffer()
总是跳过它。
next()和hasNext()方法及其基元类型伴随方法(如nextInt()或hasNextInt(())首先跳过任何与分隔符模式匹配的输入,然后尝试返回下一个令牌。hasNext和next方法都可以阻止等待进一步的输入。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;
}
您没有关闭已接受的套接字。
您不需要"特殊EOF令牌"。流的末尾是明确的。