23

I found some sample code of 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);  
     }
 }

When he gets the selected keys, he remove the key in the loop. Why we should do this?


UPDATE

Thanks to the answers provided by EJP and user270349, I think I understand it now, let me explain it in detail.

There are 2 tables in the selector:

  1. registration table: when we call channel.register, there will be a new item(key) into it. Only if we call key.cancel(), it will be removed from this table.

  2. ready for selection table: when we call selector.select(), the selector will look up the registration table, find the keys which are available, copy the references of them to this selection table. The items of this table won't be cleared by selector(that means, even if we call selector.select() again, it won't clear the existing items)

That's why we have to invoke iter.remove() when we got the key from selection table. If not, we will get the key again and again by selector.selectedKeys() even if it's not ready to use.

Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
Freewind
  • 193,756
  • 157
  • 432
  • 708
  • 1
    No need to 'imagine'. It does have those two sets: the registered keys returned by keys(), and the selected keys returned by selectedKeys(). – user207421 Aug 21 '11 at 05:51

5 Answers5

15

Because the Selector never does that, it only adds to the set, so if you don't do it you will reprocess the event yourself next time the Selector returns.

user207421
  • 305,947
  • 44
  • 307
  • 483
0

Because until you do it you can't detect new event repetitions.

aalku
  • 2,860
  • 2
  • 23
  • 44
-1

Because removing key from selected set, as it's been Handled, it will wait for next selection event.

selected set contains keys of the ready channels.

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

Get an iterator over the set of selected keys and Perform the Business Information

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

Finally Remove key from selected set; it's been handled

 it.remove(  );

Keys can be directly removed from this set, but not added. Attempting to add to the selected key set throws java.lang.UnsupportedOperationException.

anish
  • 6,884
  • 13
  • 74
  • 140
  • Very poorly expressed and two years late. – user207421 Jul 08 '14 at 23:57
  • EJP - two years late for the OP, but great for anyone else finding this post via Google later (which is the real purpose of StackOverflow, remember? to be the best source of programmer related problems / solutions). – Jakob Jenkov Feb 12 '15 at 22:42
  • 1
    @JakobJenkov So just very poorly expressed then. The first sentence is almost meaningless, and barely answers the question at all on the most generous interpretation, and the rest of it just repeats the documentation. – user207421 Feb 22 '15 at 21:27
-1

avoid publicSelectedKeys private final Set<SelectionKey> publicSelectedKeys;

liqiqi
  • 1
-3

if you dont invoke remove method you will find it also work fine。

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();

    }

}

just print it and test it 。you will find even you not remove,use key.isAcceptable() can filter corrent result

JesseLin
  • 22
  • 3
  • 4
    No you won't. You will keep getting stale events that have already been processed, so for example on OP_READ you will get zero length reads. – user207421 Apr 23 '17 at 02:10