Selector简介
Selector和Channel关系
Selector一般称为选择器,也可以翻译为多路复用器。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络连接。
使用Selector的好处在于:使用更少的线程来就可以处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。
SelectableChannel(可选择通道)
(1)不是所有的Channel都可以被Selector复用的。比方说,FileChannel就不能被选择器复用。判断一个Channel能被Selector复用,有一个前提:判断它是否继承了一个抽象类SelectableChannel。如果继承了SelectableChannel,则可以被复用,否则不能。
(2)SelectableChannel类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。所有socket通道,都继承了SelectableChannel类都是可选择的,包括从管道对象中获得的通道。而FileChannel类,没有继承SelectableChannel,因此不是可选通道。
(3)一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。通道和选择器之间的关系,使用注册的方式完成。SelectableChannel可以被注册到Selector对象上,在注册的时候,需要指定通道的哪些操作,是Selector感兴趣的。
Channel注册到Selector
(1)使用Channel.register(Selector sel, int ops)方法,将一个通道注册到一个选择器时。第一个参数,指定通道要注册的选择器。第二个参数指定选择器需要查询的通道操作。
(2)可以供选择器查询的通道操作,从类型来分,包括以下四种:
- 可读:SelectionKey.OP_READ
- 可写:SelectionKey.OP_WRITE
- 连接:SelectionKey.OP_CONNECT
- 接收:SelectionKey.OP_ACCEPT
如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:
比如:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
(3)选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。什么是操作的就绪状态?一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪,就可以被Selector查询到,程序可以对通道进行对应操作。比方说,某个SocketChannel通道可以连接到一个服务器,则处于“连接就绪”(OP_CONNECT)。再比方说,一个ServerSocketChannel服务器通道准备好接收新进入的连接,则处于“接受就绪”(OP_ACCEPT)状态。还比方说,一个有数据可读的通道,可以说是“读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。
SelectionKey(选择键)
(1)Channel注册到后,并且一旦通道处于某种就绪状态,就可以被选择器查询到。这个工作使用选择器Selector的select()方法完成。select方法的作用,对感兴趣的通道操作,进行就绪状态的查询。
(2)Selector可以不断的查询Channel中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是Selector感兴趣的操作,就会被Selector选中,放入选择键集合中。
(3)一个选择键,首先是包含了注册在Selector通道操作的类型,比方说SelectionKey.OP_READ。也包含了特定的通道与特定的选择器之间的注册关系。
开发应用程序是,选择键是编程的关键。NIO的编程,就是根据对应的选择键,进行不同的业务逻辑处理。
(4)选择键的概念,和事件的概念比较相似。一个选择键类似监听器模式里面的一个事件。由于Selector不是事件触发的模式,而是主动取查询的模式,所以不叫事件Event,而是叫SelectionKey选择键。
Selector的使用方法
Selector的创建
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
public class SelectorDemo1 {
public static void main(String[] args) throws IOException {
//创建selector
Selector selector = Selector.open();
//创建通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置非阻塞
serverSocketChannel.configureBlocking(false);
//绑定连接
serverSocketChannel.bind(new InetSocketAddress(8888));
//注册channel到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮询查询就绪操作
Set<SelectionKey> keys = selector.selectedKeys();
//遍历集合
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//判断就绪状态
if (key.isAcceptable()) {
} else if (key.isConnectable()) {
} else {
}
iterator.remove();
}
//停止选择的方法
}
}
Selector示例
客户端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
public class SelectorDemo2 {
//client
public static void main(String[] args) throws IOException {
// 1. 获取通道,绑定主机和端口号
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
// 2. 切换到非阻塞模式
socketChannel.configureBlocking(false);
// 3. 创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 4. 写入buffer
buffer.put(new Date().toString().getBytes());
// 5. 模式切换
buffer.flip();
// 6. 写入通道
socketChannel.write(buffer);
// 7. 关闭
buffer.clear();
}
}
服务端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class SelectorDemo3 {
//server
public static void main(String[] args) throws IOException {
// 1. 获取服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2. 切换非阻塞模式
serverSocketChannel.configureBlocking(false);
// 3. 创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 4. 绑定端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
// 5. 获取selector
Selector selector = Selector.open();
// 6. 通道注册到选择器,进行监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 7. 选择器进行轮询,进行后续操作
while (selector.select() > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//获取就绪操作
SelectionKey key = iterator.next();
//判断什么操作
if (key.isAcceptable()) {
//获取连接
SocketChannel accept = serverSocketChannel.accept();
//切换非阻塞模式
accept.configureBlocking(false);
//注册
accept.register(selector,SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
int len = 0;
while ((len = channel.read(buffer1)) > 0) {
buffer1.flip();
System.out.println(new String(buffer1.array(),0,len));
buffer1.clear();
}
}
}
iterator.remove();
}
}
}