天天看点

Selector.select()无法正确回调的问题Selector.select()无法正确回调的问题

Selector.select()无法正确回调的问题

我们都知道socketChannel通过register方法注册到Selector上后,Selector.select()会阻塞住,直到监听的事件发生后,select()会通过Linux的epoll方法回调,并唤醒该线程。

现在有这么一个疑问,如果开一个线程去执行Selector.select()方法,当监听的事件发生后,该线程是否会被唤醒?

以ServerSocketChannel监听accept事件为例,这里开了一个Reactor线程来执行Selector.select():

private static class Reactor extends Thread{

        @Override
        public void run() {
            try {
                select();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void select() throws IOException{
            while (true){  
                int select = selector.select();
                if (select == 0) continue;
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isAcceptable()){
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                        SocketChannel channel = serverChannel.accept();
                        System.out.println("接收到请求");
                    }
                    iterator.remove(); 
                }
            }
        }
           

而在主线程中,如果先开启这个Reactor线程,再去接收连接:

public static void main(String[] args) {
        try {
            new Reactor().start();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            InetSocketAddress address = new InetSocketAddress(8080);
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(address);
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
           

然后开一个客户端去连接该服务端,结果发现并没有输出,Reactor线程一直阻塞在selector.select()上;

但是如果先去接收请求再开启这个Reactor线程:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(8080);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(address);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
new Reactor().start();
           

结果发现会输出“接收到请求”,这是因为此时Reactor线程在执行selector.select()时由于有已就绪的,所以不会阻塞而继续往下执行。

然后再开一个客户端去连接该服务端,发现此时能够正常输出“接收到请求”。

这说明了Reactor线程在第一次执行selector.select()时,必须要有已就绪的事件,否则后面即使有了已就绪的事件,还是会阻塞在selector.select()上。

在测试监听SocketChannel时,同样也发现了这一问题。

这时如果想要正确地回调,可以使用select(time),但这样会造成CPU浪费;也可以在接收到第一个连接时,使用select.wakeup()方法,唤醒阻塞的线程。