在上一篇博文实战:ServerSocketChannel(模拟多路IO复用)我们用循环和列表实现了多路IO复用,现在我们用官方提供的Selector来实现同样的功能。
一 Selector(选择器)介绍
Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
二 Selector(选择器)的使用方法介绍
1. Selector的创建
通过调用Selector.open()方法创建一个Selector对象,如下:
Selector selector = Selector.open();
这里需要说明一下
2. 注册Channel到Selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Channel必须是非阻塞的。所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承SelectableChannel。Socket channel可以正常使用。
SelectableChannel抽象类 有一个 configureBlocking() 方法用于使通道处于阻塞模式或非阻塞模式。
abstract SelectableChannel configureBlocking(boolean block)
SelectableChannel抽象类的configureBlocking() 方法是由 AbstractSelectableChannel抽象类实现的,SocketChannel、ServerSocketChannel、DatagramChannel都是直接继承了 AbstractSelectableChannel抽象类
register() 方法的第二个参数。这是一个“ interest集合 ”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
- Connect
- Accept
- Read
- Write
通道触发了一个事件意思是该事件已经就绪。比如某个Channel成功连接到另一个服务器称为“ 连接就绪 ”。一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”。一个有数据可读的通道可以说是“ 读就绪 ”。等待写数据的通道可以说是“ 写就绪 ”。
这四种事件用SelectionKey的四个常量来表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果你对不止一种事件感兴趣,使用或运算符即可,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
select()方法介绍
在刚初始化的Selector对象中,这三个集合都是空的。 通过Selector的select()方法可以选择已经准备就绪的通道 (这些通道包含你感兴趣的的事件)。比如你对读就绪的通道感兴趣,那么select()方法就会返回读事件已经就绪的那些通道。
三、实战
这里模拟实战:ServerSocketChannel(模拟多路IO复用)这一篇博文里写一个功能一样的例子。
其实总体长得很相似
服务器端
public class Server2 {
private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
public static void main(String[] args) {
try {
//1、创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2、绑定我们的端口号
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));
//3、设置连接模式为非阻塞式
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
// 注册 channel,并且指定感兴趣的事件是 Accept
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
//4、非阻塞式不会阻塞
int readyNum = selector.select();
if (readyNum == 0) {
continue;
}
//这里就表示有准备好的消息
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()) {
SelectionKey key = it.next();
if(key.isAcceptable()) {
System.out.println("有新连接");
// 接受连接
// 创建新的连接,并且把连接注册到selector上,而且,
// 声明这个channel只对读操作感兴趣。
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//// 通道可读
SocketChannel socketChannel = (SocketChannel) key.channel();
//清除缓存中的信息
byteBuffer.clear();
int read = socketChannel.read(byteBuffer);
if(read>0) {
//Buffer切换为读模式
byteBuffer.flip();
//设定编码
Charset charset = Charset.forName("UTF-8");
String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
System.out.println("receiveText:"+receiveText);
}
}else if (key.isWritable()) {
// 通道可写
}
//写完后就移除
it.remove();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 80));
while(true) {
Scanner scanner = new Scanner(System.in);
socket.getOutputStream().write(scanner.next().getBytes());
}
}
}
结束:具体原理以后再研究啦!