SelectionKey iterator.remove()抛出UnsupportedOperationException和无限循环

时间:2013-05-14 21:56:36

标签: java json gson nio socketchannel

我有一个方法可以打开连接,查询网站,获取页面数,然后使用NIO同时检索所有页面。第一个查询是使用URLConnection完成的,并且完美无缺。当我尝试使用NIO选择器和通道时,我遇到了2个问题:

1)如果我没有从迭代器中删除密钥,则在无限循环中运行打印size()并发送查询。如果我尝试删除密钥,我会收到UnsupportedOperationsException。呸!

2)在写入套接字后,是否需要从OP_WRITE注销通道?如果是这样,我可以致电channel.register(selector, SelectionKey.OP_READ)以取消写作的兴趣吗?

public void test() throws IOException {
// create selector
Selector selector = Selector.open();
System.out.println("opened");

// get the number of pages
URL itemUrl = new URL(ITEM_URL);
URLConnection conn = itemUrl.openConnection();
conn.setDoOutput(true);
OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
// out.write(getHeaderString(itemUrl));
out.write(new Query("", "Internal Hard Drives", false, false, true, false, -1, 7603, 1, 14, -1, "", "PRICE", 1).toString());
out.close();

JsonReader in = new JsonReader(new InputStreamReader(conn.getInputStream()));
JsonParser parser = new JsonParser();
JsonObject tempObj = (JsonObject) parser.parse(in);
Pages.setNumOfPages(getNumberOfIterations(tempObj.get("PaginationInfo")));
System.out.println("Pages: " + Pages.getNumOfPages());

    // for each page, create a channel, attach to selector with interest in read
    // typically this would be i <= Pages.getNumberOfPages but to troubleshoot, i'm limiting this to just once.
for (int i = 1; i <= 1; i++) {
    SocketChannel channel = SocketChannel.open();
    channel.configureBlocking(false);
    channel.connect(new InetSocketAddress(itemUrl.getHost(), 80));
    channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
}
selector.select();
Set<SelectionKey> sk = selector.keys();

while (!sk.isEmpty()) {
    System.out.println(sk.size());
    Iterator<SelectionKey> iterator = sk.iterator();
    while (iterator.hasNext()) {
    SelectionKey key = iterator.next();

            iterator.remove();
    if (key.isReadable()) {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buf = ByteBuffer.allocate(8192);
        channel.read(buf);
        buf.flip();
        Product p = parse(buf, Product.class);
        if (p != null) {
        finalItems.add(p);
        System.out.println("Item added!");
        key.cancel();
        }
    } else if (key.isWritable()) {
        SocketChannel channel = (SocketChannel) key.channel();
        System.out.println(itemUrl);
        System.out.println(new Query("", "Internal Hard Drives", false, false, true,
            false, -1, 7603, 1, 14, -1, "", "PRICE", 1).toString());
        channel.write(ByteBuffer.wrap(new Query("", "Internal Hard Drives", false,
            false, true, false, -1, 7603, 1, 14, -1, "", "PRICE", 1).toString()
            .getBytes()));

    }
    }
    selector.select();
    sk = selector.keys();
}
}

2 个答案:

答案 0 :(得分:1)

来自http://docs.oracle.com/javase/6/docs/api/java/nio/channels/Selector.html#keys()

“密钥集不能直接修改。只有在取消密钥并且其通道已被注销后才会删除密钥。任何修改密钥集的尝试都将导致抛出UnsupportedOperationException。”

您想要使用Selector.selectedKeys();

“可以从选定密钥集中删除密钥,但不能直接添加密钥。任何将对象添加到密钥集的尝试都将导致抛出UnsupportedOperationException。”

selector.select();
Set<SelectionKey> sk = selector.selectedKeys();

然后你可以使用Iterator.remove()

在页面底部http://tutorials.jenkov.com/java-nio/selectors.html张贴了一个很好的例子

答案 1 :(得分:1)

回答(2):你应该只按如下方式注册OP_WRITE:

  1. 您有要写的数据。
  2. 执行写入,循环直到缓冲区为空或write()返回零。
  3. 如果write()返回零,则注册OP_WRITE的通道,记住缓冲区,并继续选择。
  4. 当OP_WRITE触发时,再次尝试写入,如上所述。这次,如果它不返回零,则取消注册OP_WRITE。如果它是暂时的,你现在也可以忘记那个缓冲区。
  5. 否则只需继续选择。
  6. 你只使用这样的OP_WRITE的原因几乎总是准备就绪(因为套接字发送缓冲区中几乎总是有空间),但你几乎总是不准备写。因此,永久注册OP_WRITE会导致选择器立即返回,这是无用的。

    根据您的上一段重新注册并不是更改频道注册事件的正确方法。它会创建一个新的SelectionKey,据我所知,它不会取消旧的。正确的方法是在现有的SelectionKey上调用readyOps()。

相关问题