前言
工欲善其事必先利其器, 讲了好几篇文章的Netty相关, 今天讲一下原始的 Java NIO 的Selector选择器, 本篇将从基本介绍到API相关全部介绍一遍
基本介绍
Java的NIO使用非阻塞的 IO 方式, 可以使用一个线程去处理多个客户端连接, 这个时候就会使用到Selector选择器
Selector可以检测到多个注册的通道上是否有事件发生(多个Channel以事件的方式可以注册到一个Selector上). 如果有事件发生, 就去获取事件然后针对每一个事件进行相应的处理, 这样就实现了只用一个线程去管理多个通道
通过Selector使得只有在连接真正有读写事件发生的时候, 才会进行读写, 大大减少了系统开销
在
Netty中的 IO线程NioEventLoop聚合了Selector, 可以同时并发的处理成百上千个客户端的连接
API
可以看到Selector是一个抽象类, 如图所示, 是Selector的所有方法, 接下来我们就讲解一下Selector的常用方法
网络异常,图片无法展示
|
open():得到一个选择器对象select():无超时时间的select过程, 一直等待, 直到发现有CHannel可以进行 IO操作select(long timeout):监控所有注册的Channel, 当其中的CHannel有 IO操作 可以进行时, 将这些Channel对应的SelectionKey找到, 参数用户设置超时时间wakup():唤醒selectorselectNow():不阻塞, 立即返回selectKeys():返回所有发生事件的Channel对应的SelectionKey的集合, 通过SelectionKey可以找到对应的Channelkeys():返回所有CHannel对应的SelectionKey的集合, 通过SelectionKey能找到对应的CHannel
NIO 中存在
ServerSocketChannel功能类似于ServerSocket,SocketCahnnel类似于Select
客户端发起连接时服务端工作流程
示例代码
public static void main(String[] args) throws IOException { // 异常向上抛出 ServerSocketChannel server= ServerSocketChannel.open(); // 获取一个选择器对象 Selector selector = Selector.open(); // 注册 serverSocketChannel 到 selector, 关注 OP_ACCEPT 事件 server.register(selector, SelectionKey.OP_ACCEPT); // 绑定端口 server.socket().bind(new InetSocketAddress(8888)); // 设置 serverSocketChannel 为非阻塞模式 server.configureBlocking(false); for (;;){ // 没有事件发生 if (selector.select(1000) == 0){ continue; } // 有事件发生, 找到发生事件的 Channel 对应的 SelectionKey 的集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍历 Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()){ final SelectionKey next = iterator.next(); // 发生 OP_ACCEPT 事件, 处理连接请求 if (next.isAcceptable()){ final SocketChannel accept = server.accept(); // 将 socketChannel 注册到 selector, 关注 OP_READ 事件, 并给 socketChannel 关联 Buffer accept.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(2048)); } // 如果是发生读事件, 客户端读取数据 else if (next.isReadable()){ final SocketChannel channel = (SocketChannel) next.channel(); final ByteBuffer buffer =(ByteBuffer) next.attachment(); channel.read(buffer); } // 手动从集合中移除当前的 selectionKey, 防止重复处理事件 iterator.remove(); } } } 复制代码
在上述服务端示例代码中, 服务端的工作流程为:
- 当客户端发起连接时, 会通过
ServerSocketChannel创建对应的SocketChannel - 调用
SelectChannel的注册方法将其注册到Selector上, 注册方法会返回一个SelectionKey, 将该SelectionKey放入到SelectionKeys集合中, 此时,SelectKey和Selector关联, 也和SocketChannel关联 Selector调用select(), select(timeout), selectNow()方法对内部的SelectionKeys中的SelectionKey所对应的SocketChannel进行监听- 通过
SelectionKey找到有事件发生的SocketChannel, 完成数据处理