0

我有一个方法可以打开一个连接,查询一个站点,获取页面数,然后使用 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();
}
}
4

2 回答 2

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

于 2013-05-14T23:27:03.420 回答
1

回答(2):您应该只注册 OP_WRITE 如下:

  1. 你有数据要写。
  2. 您执行写入,循环直到缓冲区为空或 write() 返回零。
  3. 如果 write() 返回零,则为 OP_WRITE 注册通道,以某种方式记住缓冲区,然后继续选择。
  4. 当 OP_WRITE 触发时,如上再次尝试写入。这一次,如果它不返回零,则取消注册 OP_WRITE。如果它是临时的,您现在也可以忘记该缓冲区。
  5. 否则就继续选择。

你只像这样使用 OP_WRITE 的原因是它几乎总是准备好(因为套接字发送缓冲区中几乎总是有空间),但你并不总是准备好写入。因此,永久注册 OP_WRITE 只会导致选择器立即返回,这是没有用的。

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

于 2013-05-15T00:19:11.360 回答