java.util.Scanner hangs on hasNext()



我在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令牌"。流的末尾是明确的。

最新更新