前言
工欲善其事必先利其器, 讲了好几篇文章的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():
唤醒selector
selectNow():
不阻塞, 立即返回selectKeys():
返回所有发生事件的Channel
对应的SelectionKey
的集合, 通过SelectionKey
可以找到对应的Channel
keys():
返回所有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
, 完成数据处理