NIO之旅Selector

灰太狼 2022-11-29 12:18 263阅读 0赞

之前的文章介绍了Buffer和Channel,今天这篇文章简单聊一下Selector

Selector是NIO中最为重要的组件之一,我们常常说的多路复用器就是指的Selector组件。Selector组件用于轮询一个或多个NIO Channel的状态是否处于可读、可写。通过轮询的机制就可以管理多个Channel,也就是说可以管理多个网络连接。

在这里插入图片描述

轮询机制
  • 首先,需要将Channel注册到Selector上,这样Selector才知道需要管理哪些Channel
  • 接着Selector会不断轮询其上注册的Channel,如果某个Channel发生了读或写的事件,这个Channel就会被Selector轮询出来,然后通过SelectionKey可以获取就绪的Channel集合,进行后续的IO操作
创建一个Selector对象
  1. // 创建一个Selector
  2. Selector selector = Selector.open();
注册Channel到Selector中
  1. // 把一个Channel注册到Selector
  2. socketChannel.configureBlocking(false);
  3. socketChannel.register(selector, SelectionKey.OP_READ);

需要注意的是,一定要使用configureBlocking(false)把Channel设置成非阻塞模式,否则会抛出IllegalBlockingModeException异常。

Channel的register有两个重载方法:

  1. SelectionKey register(Selector sel, int ops) {
  2. return register(sel, ops, null);
  3. }
  4. SelectionKey register(Selector sel, int ops, Object att);

对于ops参数,即selector要关心这个Channel的事件类型,在SelectionKey类里面有这样几个常量:

  • OP_READ 可以从Channel读数据
  • OP_WRITE 可以写数据到Channel
  • OP_CONNECT 连接上了服务器
  • OP_ACCEPT 有新的连接进来了

如果你对不止一种事件感兴趣,使用或运算符即可,如下:

  1. int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

需要注意的是,FileChannel只有阻塞模式,不支持非阻塞模式,所以它是没有register方法的!

第三个参数att是attachment的缩写,代表可以传一个“附件”进去。在返回的SelectionKey对象里面,可以获取以下对象:

  • channel():获取Channel
  • selector():获取Selector
  • attachment():获取附件
  • attach(obj):更新附件
获取可操作的Channel

并不是所有注册过的键都仍然有效,有些可能已经被cancel()方法被调用过的键。所以一般来说,我们轮询selectedKeys()方法,Selector可以返回两种SelectionKey集合:

  • keys():已注册的键的集合
  • selectedKeys():已选择的键的集合

    Set selectedKeys = selector.selectedKeys();

当有新增就绪的Channel,调用select()方法,就会将key添加到Set集合中。

完整的实例代码
  • Server

    public class Server {

    1. public static void main(String[] args) {
    2. try (
    3. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    4. Selector selector = Selector.open();
    5. ) {
    6. serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8080));
    7. serverSocketChannel.configureBlocking(false);
    8. System.out.println("server 启动...");
    9. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    10. while (selector.select() > 0) {
    11. Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
    12. while(keyIterator.hasNext()) {
    13. SelectionKey key = keyIterator.next();
    14. if (key.isReadable()) {
    15. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    16. SocketChannel socketChannel = (SocketChannel) key.channel();
    17. int readBytes = socketChannel.read(buffer);
    18. if (readBytes > 0) {
    19. buffer.flip();
    20. byte[] bytes = new byte[buffer.remaining()];
    21. buffer.get(bytes);
    22. String body = new String(bytes, StandardCharsets.UTF_8);
    23. System.out.println("server 收到:" + body);
    24. }
    25. } else if(key.isAcceptable()) {
    26. SocketChannel socketChannel = serverSocketChannel.accept();
    27. socketChannel.configureBlocking(false);
    28. socketChannel.register(selector, SelectionKey.OP_READ);
    29. }
    30. keyIterator.remove();
    31. }
    32. }
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. }

    }

  • Client

    public class Client {

    1. public static void main(String[] args) {
    2. try (
    3. SocketChannel socketChannel = SocketChannel.open();
    4. ) {
    5. socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
    6. System.out.println("client 启动...");
    7. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    8. buffer.put("hi, 这是client".getBytes(StandardCharsets.UTF_8));
    9. buffer.flip();
    10. socketChannel.write(buffer);
    11. } catch (IOException e) {
    12. e.printStackTrace();
    13. }
    14. }

    }

发表评论

表情:
评论列表 (有 0 条评论,263人围观)

还没有评论,来说两句吧...

相关阅读

    相关 NIOSelector

    之前的文章介绍了[Buffer][]和[Channel][],今天这篇文章简单聊一下`Selector`。 `Selector`是NIO中最为重要的组件之一,我们常常说的多路

    相关 NIOBuffer

    在上一篇文章中聊了一下[Channel][],这篇文章聊一下Java NIO中的另一个重要的组件:`Buffer`。 Buffer的概念 首先我们先看一下`Buffer

    相关 NIOSelector

    > Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从

    相关 Java NIOSelector

    这篇文章来介绍一下另一个比较重要的概念----Selector。我们知道系统线程的切换是消耗系统资源的,如果我们每一个连接都用一个线程来管理,资源的开销会非常大,这个时候就可以