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

To answer (2): You should only register OP_WRITE as follows:

  1. You have data to write.
  2. You do the write, looping until the buffer is empty or write() returns zero.
  3. If write() returned zero, register the channel for OP_WRITE, remember the buffer somehow, and keep selecting.
  4. When OP_WRITE fires, try the write again as above. This time, if it doesn't return zero, deregister OP_WRITE. You can now forget about that buffer too, if it was temporary.
  5. Otherwise just keep selecting.

The reason you only use OP_WRITE like this is that is almost always ready (because there is almost always space in the socket send buffer), but you aren't almost always ready to write. So, registering OP_WRITE permanently just results in the selector returning immediately, which is useless.

Re-registering as per your last paragraph isn't the correct way to change what events the channel is registered for. It creates a new SelectionKey, and, as far as I know, doesn't cancel the old one. The correct way is to call readyOps() on the existing SelectionKey.

于 2013-05-15T00:19:11.360 に答える