我遇到了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("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 :(得分:0)
您没有关闭已接受的套接字。
您不需要特殊的EOF代币'。流的结尾是明确的。
答案 1 :(得分: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中所述。
next()和hasNext()方法及其原始类型的伴随方法(例如nextInt()和hasNextInt())首先跳过与分隔符模式匹配的任何输入,然后尝试返回下一个标记。 hasNext和next方法都可能阻止等待进一步输入。 hasNext方法块是否与其关联的下一个方法是否会阻塞无关。
首先,我们跳过上次请求中仍在缓冲区中的令牌,然后我们注意到我们的缓冲区中没有任何新数据,所以我们调用readInput()
,在这种情况下只是{{} 1}},然后我们循环回\n
,再次跳过我们的分隔符!
此时服务器正在等待更多输入,客户端正在等待响应。死锁。
如果我们检查是否跳过最后一个令牌,这很容易避免......
hasTokenInBuffer()