Selector 介绍
Selector 选择器也称多路复用器,NIO三大组件之一,主要用于单线程管理多个Channel,监听channel中的事件。读写操作非阻塞,提高IO运行效率。只有在通道中出现读写事件发生才会进行读写,减少系统开销,并不会为每一个连接创建一个线程,不用维护多个线程减少线程上下文切换开销。
一个 Selector对应一个Thread 可以理解为一个Thread管理了一个Selector,而Selector下面监听了多个channel的读写事件,当发生读写事件的时候再进行读写相关的处理。
核心类与方法
Selector类核心方法
public static Selector open(); //获取一个selector
public int select(long timeout);// 获取注册到的通道 参数用来设置超时时间 返回值大于0时说明Selector 中有发生了IO操作的channel 该方法还有一个不带参数的阻塞方法,带超时的本质也是阻塞方法只是阻塞时间为超时时长
public Set<SelectorKey> selectedKeys();// 返回所有发生了IO操作的SelectionKey 而SelectorKey中关联了具体的channel
public int selectNow();// 立刻返回selector中的SelectionKey的数量
public Selector wakeup();// 唤醒selector
SelectionKey 表示了Selector和channel的注册关系
核心常量
public static final int OP_READ = 1 << 0; // 读事件
public static final int OP_WRITE = 1 << 2; // 写事件
public static final int OP_CONNECT = 1 << 3; // 连接建立事件
public static final int OP_ACCEPT = 1 << 4;// 可以连接事件
核心方法
public Selector selector();// 通过SelectionKey获取Selector
public SelectableChannel channel();// 获取SelectionK绑定的Channel
public SelectionKey interestOps(int ops);// 改变监听事件
public boolean isAccetable(); // 是否可接受连接
public boolean isReadable(); // 是否可读
public boolean isWritable(); //是否可写
//ServerSocketChannel Nio中处理ServerSocket服务端核心类 类似传统ServerScoket
// 核心方法
public static ServerSocketChannel open();// 静态方法返回一个ServerSocketChannel
public ServerSocketChannel bind(SocketAddress adress);// 设置服务绑定的端口
public SelectableChannel configureBlocking(boolean block);// 设置为阻塞或者非阻塞
public SelectionKey register(Selector sel,int ops);// 注册到一个Selector中 传入关注的事件类型
//SocketChannel Nio中处理Socket客户端的核心类 类似传统的Socket
// 核心方法
public static SocketChannel open();// 静态方法返回一个SocketChannel
public SelectableChannel configureBlocking(boolean block);// 设置为阻塞或者非阻塞
public boolean connect(SocketAddress address);// 连接一个Socket服务器
public boolean finishConnect();// 是否连接完成
public int write(ByteBuffer buffer);// 向通道中写入数据
public int read(ByteBuffer buffer);// 将通道中的数据读到buffer中
public SelectionKey register(Selector sel,int ops,Object attament);// 注册到Selector中 最后一个参数可以附加一个共享的数据
代码演示
// ServerSocket端
public class SelectorServerApiTest {
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 创建一个selector
Selector selector = Selector.open();
// 调用channel register 方法将 channel注册到selector 中
/**
* 参数一:selector 主要注册到的selector
* 参数二:注册到selector 关心的模式 SelectionKey中的常量
* 返回当前注册到select中的唯一Key
*/
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int select = selector.select(1000);
// 如果select方法返回0 说明 selector 中没有注册任何通道
if (select == 0) {
System.out.println("服务器等待1秒,没有客户端连接");
continue;
}
// 如果select 方法返回不为0 说明可以获取到注册到select中通道关心的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 如果发生是OP_ACCEPT事件,表明有新客户端连接
if (key.isAcceptable()) {
// 获取到客户端的channel
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置非阻塞
socketChannel.configureBlocking(false);
// 注册到selector 中 关注READ事件 并关联一个buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {
// 如果发生的读事件 调用SelectorKey的.channel方法获取channel 并强制转换成SocketChannel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 拿到之前注册到selector 上的 附加buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
// 将数据读到buffer中
socketChannel.read(buffer);
System.out.println(new String(buffer.array()));
}
// 移除迭代器中的key 防止重复操作
it.remove();
}
}
}
}
// Socket客户端
public class SelectClientApiTest {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.socket().connect(new InetSocketAddress("localhost",9999));
socketChannel.configureBlocking(false);
if(!socketChannel.isConnected()){
while (!socketChannel.finishConnect()){
System.out.println("socket channel 连接上 Server SocketChannel");
}
}
ByteBuffer byteBuffer = ByteBuffer.wrap("hello Nio".getBytes());
socketChannel.write(byteBuffer);
System.in.read();
}
}