java.util.Scanner挂在hasNext()上

时间:2015-03-18 21:33:38

标签: java sockets java.util.scanner serversocket hang

我遇到了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();
    }
}

2 个答案:

答案 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()