Подтвердить что ты не робот

Почему ключ должен быть удален в `selector.selectedKeys(). Iterator()` в java nio?

Я нашел пример кода java nio:

 ServerSocketChannel server = ServerSocketChannel.open();  
 Selector selector = Selector.open();  
 server.socket().bind(new InetSocketAddress(8080));  
 server.configureBlocking(false); 
 server.register(selector, SelectionKey.OP_ACCEPT);  

 while(true) {
     selector.select(); 
     Iterator iter = selector.selectedKeys().iterator();  
     while (iter.hasNext()) {  
         SelectionKey key = (SelectionKey) iter.next();  
         iter.remove();  // Why remove it? 
         process(key);  
     }
 }

Когда он получает выбранные ключи, он удаляет ключ в цикле. Почему мы должны это делать?


UPDATE

Благодаря ответам EJP и user270349, я думаю, что сейчас понимаю, позвольте мне объяснить это подробно.

В селекторе есть 2 таблицы:

  • регистрационная таблица: при вызове channel.register в нее будет добавлен новый элемент (ключ). Только если мы назовем key.cancel(), он будет удален из этой таблицы.

  • готов к таблице выбора: когда мы вызываем selector.select(), селектор будет искать таблицу регистрации, найти доступные ключи, скопировать ссылки на них в эту таблицу выбора. Элементы этой таблицы не будут очищаться с помощью селектора (это означает, что даже если мы снова назовем selector.select(), он не очистит существующие элементы)

Вот почему мы должны вызывать iter.remove(), когда мы получили ключ из таблицы выбора. Если нет, мы снова и снова получаем ключ selector.selectedKeys(), даже если он не готов к использованию.

4b9b3361

Ответ 1

Поскольку селектор никогда этого не делает, он только добавляет к набору, поэтому, если вы этого не сделаете, вы будете перерабатывать событие самостоятельно в следующий раз, когда Selector вернется.

Ответ 2

Потому что пока вы это сделаете, вы не сможете обнаружить новые повторения событий.

Ответ 3

Ваш вопрос касается использования Iterator:
https://www.tutorialspoint.com/java/java_using_iterator.htm

О Selector и возвращенный метод Set<SelectionKey> by selectedKeys():

[...] Набор выбранных клавиш - это набор клавиш, так что каждый канал канала было обнаружено, что оно готово для по меньшей мере одной из идентифицированных операций в ключевом вопросе, установленном во время предыдущей операции выбора [...]

https://docs.oracle.com/javase/7/docs/api/java/nio/channels/Selector.html

Вы можете использовать следующий код для перебора "готовых" каналов:

while (true) {
    selectorRead.select();
    for (SelectionKey key : selectorRead.selectedKeys()) {
        DatagramChannel dc = (DatagramChannel) key.channel();
    }
}

Ответ 4

Поскольку удаление ключа из выбранного набора, так как оно обрабатывается, оно будет ждать следующего события выбора.

выбранный набор содержит ключи готовых каналов.

 selector.select(); //This may block for a long time. Upon returning, the elected set contains keys of the ready channels.

Получить итератор по набору выбранных ключей и выполнить бизнес-информацию

Iterator it = selector.selectedKeys().iterator(  );

Наконец, удалите ключ из выбранного набора; он был обработан

 it.remove(  );

Ключи могут быть удалены непосредственно из этого набора, но не добавлены. Попытка добавить к выбранному набору ключей вызывает java.lang.UnsupportedOperationException.

Ответ 5

если вы не вызываете метод удаления, вы обнаружите, что он также работает нормально.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SuppressWarnings({ "unchecked" })
public class NioServer {
    ServerSocketChannel serverChannel;
    ServerSocket serverSocket;
    public final int port;
    private Selector selector;
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    NioServer(final int port) {
        this.port = port;
    }
    void init() throws Exception {
        // 创建 ServerSocketChannel、ServerSocket
        serverChannel = ServerSocketChannel.open();
        serverSocket = serverChannel.socket();
        serverSocket.bind(new InetSocketAddress(port));
        // 设置通道为非阻塞模式
        serverChannel.configureBlocking(false);
        // 开启通道选择器,并注册 ServerSocketChannel
        selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    void go() throws Exception {
        while (true) {
            int num = selector.select();
            if (num <= 0)
                continue;
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
            System.out.println(selector.selectedKeys().size()); //等于线程数量
            while (keyIter.hasNext()) {
                final SelectionKey key = keyIter.next();
                System.out.println("所有的key"+key);
                // 接收一个Socket连接
                // key.isAcceptable()如果为true,说明channnel支持accept(),也就是说明是一个ServerSocketChannel
                if (key.isAcceptable()) {
                    System.out.println("可以连接的key:"+key);
                    SocketChannel clientChannel = serverChannel.accept();
                    if (clientChannel != null) {
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    }
                }
                // 如果isReadable()为true,说明是一个SocketChannel
                if (key.isReadable()) {
                    String requestContent = read(key);
                    // 业务处理
                    // responseContent=doSomthing(requestContent);
                    write(key, "ok" /* responseContent */);
                }
//          keyIter.remove();
            }
        }
    }

    // 从通道读取数据
    String read(SelectionKey key) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();// 这一步必须有
        int len = 0;
        StringBuffer str = new StringBuffer();
        while ((len = socketChannel.read(buffer)) > 0) {
            byte[] bs = buffer.array();
            String block = new String(bs, 0, len);
            System.out.println("Server read: " + block);
            str.append(block);

        }
        buffer.clear();
        return str.toString();
    }

    // 写数据到通道

    void write(SelectionKey key, String str) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();
        buffer.put(str.getBytes());
        buffer.flip();// 这一步必须有
        socketChannel.write(buffer);

    }

    public static void main(String[] args) throws Exception {
        final int port = 10000;
        NioServer server = new NioServer(port);
        server.init();
        /// ========================================================
        // 接下来模拟3个Client并发访问服务器
        int poolsize = 1;
        ExecutorService pool = Executors.newFixedThreadPool(poolsize);
        Collection<Callable> tasks = new ArrayList<Callable>(10);
        final String clientname = "clientThread";
        for (int i = 0; i < poolsize; i++) {
            final int n = i;
            // 若每一个Client都保持使用BIO方式发送数据到Server,并读取数据。
            tasks.add(new Callable() {
                @Override
                public Object call() throws Exception {
                    Socket socket = new Socket("127.0.0.1", port);
                    final InputStream input = socket.getInputStream();
                    final OutputStream out = socket.getOutputStream();
                    final String clientname_n = clientname + "_" + n;
                    // BIO读取数据线程
                    new Thread(clientname_n + "_read") {
                        @Override
                        public void run() {
                            byte[] bs = new byte[1024];
                            while (true) {
                                try {

                                    Thread.sleep(1000);

                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                int len = 0;
                                try {
                                    while ((len = input.read(bs)) != -1) {

                                        System.out.println("Clinet thread " + Thread.currentThread().getName()
                                                + " read: " + new String(bs, 0, len));
                                    }

                                } catch (IOException e) {

                                    e.printStackTrace();
                                }
                            }
                        }

                    }.start();
                    // BIO写数据线程
                    new Thread(clientname_n + "_write") {
                        @Override
                        public void run() {
                            int a = 0;
                            while (true) {
                                try {
                                    Thread.sleep(100);

                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }

                                String str = Thread.currentThread().getName() + " hello, " + a;
                                try {
                                    out.write(str.getBytes());
                                    a++;

                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }

                    }.start();

                    return null;

                }

            });

        }
        pool.invokeAll((Collection<? extends Callable<Object>>) tasks);
        server.go();

    }

}

просто распечатайте и протестируйте его. Вы обнаружите, что даже вы не удаляете, используйте key.isAcceptable() может фильтровать результат в виде корня