NIO-selector选择器

概述

  • Selector是NIO中实现I/O多路复用的关键类。Selector 实现了通过一个线程管理多个Channel,从而管理多个网络连接的目的。
  • 要使用Selector,得向Selector注册Channel,然后调用它的select() 方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

流程

在这里插入图片描述

Selector

  • Selector 能够在单个线程中处理多个通道,这样可以减少多个线程造成上下文切换问题。Channel 需要注册到Selectors 上,一个Selectors 可以注册多个 Channel
  • Channel 注册到Selector 后会返回一个SelectionKey 对象,因为可注册多个Channel,返回的是一个 SelectorKey 集合(select()方法返回)
  • SelectionKey 将注册测Channel 与 Selectors 建立关系,通过 SelectorKey 可返现获取通道和事件信息。SelectionKey 维护着两个很重要的属性:interestOps、readyOps

interestOps 记录Selectors 监听通道的哪些事件,感兴趣的事件设置到该字段,这样在selection 操作时,当发现该Channel 有感兴趣的事件发生时,就会将感兴趣的事件再设置到readyOps 中,这样 Selectors 就能得知是哪些事件发生了以做相应处理。

事件

为了实现Selector 管理多个SocketChannel,必须将具体的SocketChannel 对象注册到Selector,并声明需要监听的事件(这样Selector 才知道需要记录什么数据),一共有4种事件:

  1. connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT(8)
  2. accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT(16)
  3. read:读事件,对应值为SelectionKey.OP_READ(1)
  4. write:写事件,对应值为SelectionKey.OP_WRITE(4)

每次请求到达服务器,都是从connect开始,connect 成功后,服务端开始准备accept,准备就绪,开始读数据,并处理,最后写回数据返回。

Selector 服务器端原理

  1. 创建ServerSocketChannel 实例,并绑定制定端口
  2. 创建Selector 实例
  3. 将serverSocketChannel 注册到Selector,并指定事件OP_ACCEPT, 最底层的socket 通过channel 和selector 建立关联
  4. 如果没有准备号的socket,select 方法会被阻塞一段时间并返回0
  5. 如果底层有socket 已经准备好,selecto r的select 方法会返回socket 的个数,而且selectedKeys 方法会返回socket 对应的事件(connect、accept、read or write)
  6. 根据不同的事件类型,进行不同的处理逻辑
  • 服务器端实现

public static void main(String[] args) throws Exception {
   //创建serversocketchannel
   ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

   //selector
   Selector selector = Selector.open();
   //绑定端口,在服务器监听
   serverSocketChannel.socket().bind(new InetSocketAddress(9527));

   //设置非阻塞
   serverSocketChannel.configureBlocking(false);
   //serverSocketChannel 注册到selector,关心事件为 连接事件
   serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

   ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

   //循环等客户端连接
   while (true) {
       //等待1秒,如果没有事件发生,返回
       int select = selector.select(1000);
       if (select == 0) {
           System.out.println("服务器等待1秒,无连接");
           continue;
       } 
       /**
       如果返回非0,获取关注的 selectionKeys 集合
        * 1.果果kes>0 获取到了练级的key
        * 2.通过 selectionKeys 反向获取通道
        */
       Set<SelectionKey> selectionKeys = selector.selectedKeys();
       if (selectionKeys.size() > 0) {
           Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
           while (selectionKeyIterator.hasNext()) {
               //获取selectKey
               SelectionKey key = selectionKeyIterator.next();
               //根据key 获取响应的通道,判断发生的事件
               if (key.isAcceptable()) {
                   System.out.println("有新的客户端连接...");
                   //为该链接生成socketChannel
                   SocketChannel socketChannel = serverSocketChannel.accept();
                   //设置非阻塞
                   socketChannel.configureBlocking(false);
                   //当前的socketChannel 注册到selector, 关注事件为read, socketChannel 关联一个buffer
                   socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
               }
               //读操作
               if (key.isReadable()) {
                   //通过key 反向获取SocketChannel
                   SocketChannel socketChannel = (SocketChannel) key.channel();
                   //获取socketChannel 关联的 bytebuff
                   ByteBuffer buffer = (ByteBuffer) key.attachment();
                   //读取buffer 到channel
                   int read = socketChannel.read(buffer);
                   System.out.println("客户端读取数据: "+new String(buffer.array()));
               }
               //删除当前key ,方式重读读写
               selectionKeyIterator.remove();
           }
       }
   }
}

Selector 客户端原理

  1. 客户端通过 SocketChannel 创建一个连接,客户端未与服务器建立连接时,不会阻塞,仍可以做其他事情
  2. 如果客户端发送数据,会触发read 事件,这样下次轮询调用select 方法时,就能通过socketChannel 读取数据,同时在selector上注册该socketChannel 的OP_WRITE事件,实现服务器往客户端写数据
public class NIOClient {

    public static void main(String[] args) throws Exception {

        //创建一个客户端连接
        SocketChannel socketChannel = SocketChannel.open();

        //建立与服务连接,与服务器监听端口一致
        InetSocketAddress inetSocketAddress = new InetSocketAddress("1270.0.0.1", 9527);

        //未连接到服务器
        while (!socketChannel.connect(inetSocketAddress)){
            if(!socketChannel.finishConnect()){
                System.out.println("未连接值服务器,不阻塞,可以去做其它事情...");
            }
        }

        //连接到服务器,发送消息
        String str = "nio client test...";
        socketChannel.write(ByteBuffer.wrap(str.getBytes()));
        //关闭连接
        socketChannel.close();
    }
}

NIO中实现非阻塞IO的核心设计Selector,Selector就是注册各种IO事件的地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值