Java NIO——selector


Java NIO编程实例之三Selector

1.概念

选择器(Selector) 是 SelectableChannle 对象的多路复用器, Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。 Selector 是非阻塞 IO 的核心。
Selector、SelectionKey和SelectableChannel,它们之间的关系如下图所示:
在这里插入图片描述
SelectableChannel是一类可以与Selector进行配合的通道,例如Socket相关通道以及Pipe产生的通道都属于SelectableChannel。这类通道可以将自己感兴趣的操作(例如read、write、accept和connect)注册到一个
Selector是一个控制器,它负责管理已注册的多个SelectableChannel,当这些通道的某些状态改变时,Selector会被唤醒(从select()方法的阻塞中),并对所有就绪的通道进行轮询操作。

2 selectionKey

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定可以监听的事件类型(可使用 SelectionKey 的四个常量表示):

  • 读 : SelectionKey.OP_READ (1)
  • 写 : SelectionKey.OP_WRITE (4)
  • 连接 : SelectionKey.OP_CONNECT (8)
  • 接收 : SelectionKey.OP_ACCEPT (16)
    若注册时不止监听一个事件,则可以使用“位或”操作符连接。
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE;

selectionKey常用方法

方法 功能
int interestOps() 返回所感兴趣的事件集合
int readyOps() 返回已经准备就绪的集合
boolean isAcceptable() 接收是否就绪
boolean isConnectable() 连接是否就绪
boolean isReadable() 读操作是否就绪
boolean isWritable 写操作是否就绪
Channel channel 获取通道
Selector selector 获取选择器
attach(Object o) 将对象或数据附着到SelectionKey上
Object attachment() 获取附着对象

3 常用方法

3.1 register(Selector selector,int ops)

SelectionKey register(Selector sel, int ops)

方法的返回值是一个SelectionKey,这个对象会被自动加入Selector的keys集合,因此不必特意保留这个SelectionKey的对象引用,需要时可以使用Selector的keys()方法得到所有的SelectionKey对象引用。
注册完成后,该通道就与Selector保持关联了。当通道的状态改变时,其改变会自动被Selector感知,并在Selector的三个集合中反应出来。

3.2 三个集合

  • Set keys(),获取keys集合,存储了所有与selector关联的Selectionkey对象
  • Set selectedKeys(),获取selectedKeys集合,存储了在一次select()方法调用后,所有状态改变的通道关联的SelectionKey对象
  • cancelledKeys集合,存储了一轮select()方法调用过程中,所有被取消但还未从keys中删除的SelectionKey对象

3.3 select方法

  • int select(),会一直阻塞,直到至少有一个注册的通道状态改变,才会被唤醒
  • int select(long timeout),一直阻塞,直到时间耗尽,或者有通道的状态改变。

3.4 wakeUp

3.5 close

4 demo

public class SelectorServer {
    private static final int PORT = 1234;
    private static ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(PORT));
            ssc.configureBlocking(false);
            //1.register()
            Selector selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("REGISTER CHANNEL , CHANNEL NUMBER IS:" + selector.keys().size());

            while (true) {
                //2.select()
                int n = selector.select();
                if (n == 0) {
                    continue;
                }
                //3.轮询SelectionKey
                Iterator<SelectionKey> iterator = (Iterator) selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //如果满足Acceptable条件,则必定是一个ServerSocketChannel
                    if (key.isAcceptable()) {
                        ServerSocketChannel sscTemp = (ServerSocketChannel) key.channel();
                        //得到一个连接好的SocketChannel,并把它注册到Selector上,兴趣操作为READ
                        SocketChannel socketChannel = sscTemp.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("REGISTER CHANNEL , CHANNEL NUMBER IS:" + selector.keys().size());
                    }
                    //如果满足Readable条件,则必定是一个SocketChannel
                    if (key.isReadable()) {
                        //读取通道中的数据
                        SocketChannel channel = (SocketChannel) key.channel();
                        readFromChannel(channel);
                    }
                    //4.remove SelectionKey
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void readFromChannel(SocketChannel channel) {
        buffer.clear();
        try {
            while (channel.read(buffer) > 0) {
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                System.out.println("READ FROM CLIENT:" + new String(bytes));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

首先注册了一个ServerSocketChannel,它用来监听1234端口上的连接;当监听到连接时,把连接上的SocketChannel再注册到Selector上,这些SocketChannel注册的是SelectionKey.OP_READ事件;当这些SocketChannel状态变为可读时,读取数据并显示。

public class SelectorClient {
    static class Client extends Thread {
        private String name;
        private Random random = new Random(47);

        Client(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                SocketChannel channel = SocketChannel.open();
                channel.configureBlocking(false);
                channel.connect(new InetSocketAddress(1234));
                while (!channel.finishConnect()) {
                    TimeUnit.MILLISECONDS.sleep(100);
                }
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                for (int i = 0; i < 5; i++) {
                    TimeUnit.MILLISECONDS.sleep(100 * random.nextInt(10));
                    String str = "Message from " + name + ", number:" + i;
                    buffer.put(str.getBytes());
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        channel.write(buffer);
                    }
                    buffer.clear();
                }
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(new Client("Client-1"));
        executorService.submit(new Client("Client-2"));
        executorService.submit(new Client("Client-3"));
        executorService.shutdown();
    }
}

猜你喜欢

转载自blog.csdn.net/ccoran/article/details/84951059